diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 94a7a0830f..42b8ca2023 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -162,7 +162,7 @@ jobs: id: retry continue-on-error: true run: | - py.test -sv tests/integration/ + py.test -sv --lf tests/integration/ - name: set status if: always() run: | diff --git a/frontend/animal/EndpointForm/constants.js b/frontend/animal/EndpointForm/constants.js index 0e494b2470..49bda5954c 100644 --- a/frontend/animal/EndpointForm/constants.js +++ b/frontend/animal/EndpointForm/constants.js @@ -6,11 +6,11 @@ const termUrlLookup = { name_term_id: "/vocab/api/ehv/endpoint_name/?format=json", }, textUrlLookup = { - system_term_id: "/selectable/animal-endpointsystemlookup/?limit=100", - organ_term_id: "/selectable/animal-endpointorganlookup/?limit=100", - effect_term_id: "/selectable/animal-endpointeffectlookup/?limit=100", - effect_subtype_term_id: "/selectable/animal-endpointeffectsubtypelookup/?limit=100", - name_term_id: "/selectable/animal-endpointnamelookup/?limit=100", + system_term_id: "/autocomplete/animal-endpointautocomplete/?field=system", + organ_term_id: "/autocomplete/animal-endpointautocomplete/?field=organ", + effect_term_id: "/autocomplete/animal-endpointautocomplete/?field=effect", + effect_subtype_term_id: "/autocomplete/animal-endpointautocomplete/?field=effect_subtype", + name_term_id: "/autocomplete/animal-endpointautocomplete/?field=name", }, label = { system: "System", diff --git a/frontend/animal/EndpointListApp/Plot.js b/frontend/animal/EndpointListApp/Plot.js index c730879c39..f82bfc36f3 100644 --- a/frontend/animal/EndpointListApp/Plot.js +++ b/frontend/animal/EndpointListApp/Plot.js @@ -151,11 +151,11 @@ const dodgeLogarithmic = (data, x, radius, options) => { // get max range for each group to determine spacing _.each(filteredData, d => { - dodgeLogarithmic(d, x, itemRadius, { + dodgeLogarithmic(d.values, x, itemRadius, { approximateXValues: settings.approximateXValues, twoSided: true, }); - range = d3.extent(d, d => d.y); + range = d3.extent(d.values, el => el.y); if (range[1] - range[0] > maxYRange) { maxYRange = Math.ceil(range[1] - range[0]); } @@ -163,7 +163,7 @@ const dodgeLogarithmic = (data, x, radius, options) => { let systems = _.chain(filteredData) .map(d => { - return {name: d[0].data.system, median: d3.mean(d, el => el.dose)}; + return {name: d.key, median: d3.mean(d.values, el => el.dose)}; }) .sortBy(d => d.median) .value(), @@ -173,9 +173,9 @@ const dodgeLogarithmic = (data, x, radius, options) => { .range([0, systems.length * maxYRange * 1.2]) .padding(0.3); - _.each(filteredData, data => { - let addition = yGroupScale(data[0].data.system); - _.each(data, d => (d.y = d.y + addition)); + _.each(filteredData, d => { + let addition = yGroupScale(d.key); + _.each(d.values, el => (el.y = el.y + addition)); }); // Reset y domain using new data @@ -183,7 +183,7 @@ const dodgeLogarithmic = (data, x, radius, options) => { y.domain([0, maxY]); yGroupScale.range([yBaseMaxRange, 0]); - filteredData = _.flatten(filteredData); + filteredData = _.flatten(_.map(filteredData, "values")); itemsGroup .selectAll(".critical-dose-group-g") @@ -266,7 +266,7 @@ const dodgeLogarithmic = (data, x, radius, options) => { .attr("cy", d => y(d.y)) .attr("r", itemRadius); - bindTooltip($tooltip, items, d => , { + bindTooltip($tooltip, items, (e, d) => , { mouseEnterExtra: () => d3.select(event.target).moveToFront(), }); }, diff --git a/frontend/mgmt/TaskTable/components/UserAutocomplete.js b/frontend/mgmt/TaskTable/components/UserAutocomplete.js index d5a3fe6ee8..32215c79bb 100644 --- a/frontend/mgmt/TaskTable/components/UserAutocomplete.js +++ b/frontend/mgmt/TaskTable/components/UserAutocomplete.js @@ -11,7 +11,7 @@ class UserAutocomplete extends Component { display: task.owner ? task.owner.full_name : null, id: task.owner ? task.owner.id : null, }, - submitUrl = `${url}?related=${task.study.assessment.id}`; + submitUrl = `${url}?assessment_id=${task.study.assessment.id}`; return (
diff --git a/frontend/shared/components/Autocomplete/Autocomplete.css b/frontend/shared/components/Autocomplete/Autocomplete.css index 35f430abd0..2238bbe420 100644 --- a/frontend/shared/components/Autocomplete/Autocomplete.css +++ b/frontend/shared/components/Autocomplete/Autocomplete.css @@ -3,13 +3,6 @@ } .autocomplete__input { - width: auto; - padding: 10px 20px; - font-family: Verdana, Arial, sans-serif; - font-size: 16px; - border: 1px solid #aaa; - border-radius: 4px; - -webkit-appearance: none; } .autocomplete__input:focus { @@ -20,27 +13,17 @@ display: none; } -.autocomplete__container--open .autocomplete__input { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - .autocomplete__suggestions-container { display: none; - width: auto; + width: 100%; } .autocomplete__container--open .autocomplete__suggestions-container { display: block; position: absolute; - top: 30px; + top: 45px; border: 1px solid #aaa; background-color: #fff; - font-size: 14px; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - border-top-left-radius: 4px; - border-top-right-radius: 4px; z-index: 2; } @@ -52,21 +35,12 @@ .autocomplete__suggestion { cursor: pointer; - padding: 0.2em 0.4em; - font-family: Verdana, Arial, sans-serif; - font-size: 1.1em; - font-weight: 400; line-height: 1.5; + padding-left: 5px; } .autocomplete__suggestion--highlighted { - background: #dadada - url(http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/themes/base/images/ui-bg_glass_75_dadada_1x400.png) - 50% 50% repeat-x; - border: 1px solid #999999; - border-radius: 4px; - color: #212121; - font-weight: normal; + background: #dadada; } .autocomplete__section-title { @@ -82,9 +56,7 @@ .autocomplete-error { border: 1px solid #cd0a0a; - background: #fef1ec - url(http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/themes/base/images/ui-bg_glass_95_fef1ec_1x400.png) - 50% 50% repeat-x; + background: #fef1ec; color: #cd0a0a; } diff --git a/frontend/shared/components/Autocomplete/ClientSide.js b/frontend/shared/components/Autocomplete/ClientSide.js new file mode 100644 index 0000000000..c92a89da5a --- /dev/null +++ b/frontend/shared/components/Autocomplete/ClientSide.js @@ -0,0 +1,63 @@ +import React, {Component} from "react"; +import ReactDOM from "react-dom"; +import PropTypes from "prop-types"; +import Autosuggest from "react-autosuggest"; +import {theme} from "./constants"; +import h from "shared/utils/helpers"; + +class ClientSideAutosuggest extends Component { + /* + Client-side autocomplete; all possibilities are passed to the component via an input prop; + the component just filters possibilities based on typing. + */ + constructor(props) { + super(props); + this.state = { + value: props.value, + suggestions: [], + }; + } + render() { + const {name, options} = this.props; + const {value, suggestions} = this.state; + return ( + { + const qry = h.escapeRegexString(value.trim()), + regex = new RegExp(qry, "i"), + suggestions = + qry.length == 0 + ? options + : options.filter(v => regex.test(v)).slice(0, 30); + this.setState({suggestions}); + }} + onSuggestionsClearRequested={() => { + this.setState({suggestions: []}); + }} + getSuggestionValue={d => d} + renderSuggestion={d => {d}} + inputProps={{ + className: "form-control", + name, + value, + onChange: (event, {newValue}) => { + this.setState({value: newValue}); + }, + }} + theme={theme} + /> + ); + } +} +ClientSideAutosuggest.propTypes = { + name: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + options: PropTypes.arrayOf(PropTypes.string).isRequired, +}; + +const renderClientSideAutosuggest = function(el, name, value, options) { + ReactDOM.render(, el); +}; + +export {ClientSideAutosuggest, renderClientSideAutosuggest}; diff --git a/frontend/shared/components/Autocomplete/index.js b/frontend/shared/components/Autocomplete/index.js index 302576af42..43e9aa6b81 100644 --- a/frontend/shared/components/Autocomplete/index.js +++ b/frontend/shared/components/Autocomplete/index.js @@ -5,12 +5,12 @@ import AutoSuggest from "react-autosuggest"; import h from "shared/utils/helpers"; import {DEFAULT_MIN_SEARCH_LENGTH, DEBOUNCE_MS, theme} from "../Autocomplete/constants"; +import {ClientSideAutosuggest, renderClientSideAutosuggest} from "./ClientSide"; import "./Autocomplete.css"; class Autocomplete extends Component { /* - An autocomplete field for use with django-selectable where the label is text but the value is an int, - using pagination + An autocomplete field for use with django-autocomplete-light */ constructor(props) { super(props); @@ -30,9 +30,9 @@ class Autocomplete extends Component { return; } - fetch(`${url}&term=${value}`, h.fetchGet) + fetch(`${url}&q=${value}`, h.fetchGet) .then(response => response.json()) - .then(json => this.setState({suggestions: json.data})); + .then(json => this.setState({suggestions: json.results})); } render() { @@ -52,8 +52,8 @@ class Autocomplete extends Component { this.setState({currentId: suggestion.id}); onChange(suggestion); }} - getSuggestionValue={suggestion => suggestion.value} - renderSuggestion={suggestion => {suggestion.value}} + getSuggestionValue={suggestion => suggestion.text} + renderSuggestion={suggestion => {suggestion.text}} inputProps={{ placeholder, className: "form-control", @@ -83,4 +83,5 @@ Autocomplete.propTypes = { minSearchLength: PropTypes.number, }; +export {ClientSideAutosuggest, renderClientSideAutosuggest}; export default Autocomplete; diff --git a/frontend/shared/components/AutocompleteSelectableText.js b/frontend/shared/components/AutocompleteSelectableText.js index 6137035bd8..8ae59cdc1d 100644 --- a/frontend/shared/components/AutocompleteSelectableText.js +++ b/frontend/shared/components/AutocompleteSelectableText.js @@ -14,7 +14,7 @@ import "./Autocomplete/Autocomplete.css"; class AutocompleteSelectableText extends Component { /* - Autocomplete widget; works with `hawc.apps.common.lookups.DistinctStringLookup` + Autocomplete widget; works with django-autocomplete-light */ constructor(props) { @@ -33,11 +33,11 @@ class AutocompleteSelectableText extends Component { return; } - const queryUrl = `${url}&term=${value}`; + const queryUrl = `${url}&q=${value}`; fetch(queryUrl, h.fetchGet) .then(response => response.json()) .then(json => { - const values = json.data.map(d => d.value); + const values = json.results.map(d => d.text); this.setState({suggestions: values}); }); } diff --git a/frontend/shared/smartTags/QuillSmartTagModal.js b/frontend/shared/smartTags/QuillSmartTagModal.js index b28abbdf8f..3014aa9424 100644 --- a/frontend/shared/smartTags/QuillSmartTagModal.js +++ b/frontend/shared/smartTags/QuillSmartTagModal.js @@ -76,16 +76,16 @@ class SmartTagModal { switch (type) { case "study": - pk = m.find("#id_study_1").val(); + pk = m.find("#id_study").val(); break; case "endpoint": - pk = m.find("#id_endpoint_1").val(); + pk = m.find("#id_endpoint").val(); break; case "data_pivot": - pk = m.find("#id_data_pivot_1").val(); + pk = m.find("#id_data_pivot").val(); break; case "visual": - pk = m.find("#id_visual_1").val(); + pk = m.find("#id_visual").val(); break; } diff --git a/frontend/shared/utils/HAWCUtils.js b/frontend/shared/utils/HAWCUtils.js index 80a2ee72a0..a83a28d93e 100644 --- a/frontend/shared/utils/HAWCUtils.js +++ b/frontend/shared/utils/HAWCUtils.js @@ -44,11 +44,11 @@ class HAWCUtils { } static InitialForm(config) { - var selector_val = config.form.find("#id_selector_1"), + var selector = config.form.find("select[name='selector']"), submitter = config.form.find("#submit_form"); submitter.on("click", function() { - var val = parseInt(selector_val.val(), 10); + var val = parseInt(selector.select2("data")[0].id); if (val) { submitter.attr("href", `${config.base_url}?initial=${val}`); return true; diff --git a/frontend/shared/utils/HawcTooltip.js b/frontend/shared/utils/HawcTooltip.js index 32061677cf..4b0c5b6854 100644 --- a/frontend/shared/utils/HawcTooltip.js +++ b/frontend/shared/utils/HawcTooltip.js @@ -18,8 +18,6 @@ class HawcTooltip { tooltip = $('
- +
-
- @@ -51,27 +46,9 @@ {% block extrajs %} {% endblock extrajs %} diff --git a/hawc/apps/animal/templates/animal/endpoint_form.html b/hawc/apps/animal/templates/animal/endpoint_form.html index 2f588be16f..621f8246a6 100644 --- a/hawc/apps/animal/templates/animal/endpoint_form.html +++ b/hawc/apps/animal/templates/animal/endpoint_form.html @@ -1,13 +1,8 @@ {% extends 'assessment-rooted.html' %} {% load add_class %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %} {% crispy form %} diff --git a/hawc/apps/animal/templates/animal/endpoint_list.html b/hawc/apps/animal/templates/animal/endpoint_list.html index b21ea73755..2eaf64ff9b 100644 --- a/hawc/apps/animal/templates/animal/endpoint_list.html +++ b/hawc/apps/animal/templates/animal/endpoint_list.html @@ -1,7 +1,6 @@ {% extends 'assessment-rooted.html' %} {% load crispy_forms_tags %} -{% load selectable_tags %} {% block content %}

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

diff --git a/hawc/apps/animal/templates/animal/endpoint_list_v2.html b/hawc/apps/animal/templates/animal/endpoint_list_v2.html index c918320a0f..2a9239871b 100644 --- a/hawc/apps/animal/templates/animal/endpoint_list_v2.html +++ b/hawc/apps/animal/templates/animal/endpoint_list_v2.html @@ -1,6 +1,5 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load add_class %} {% load url_replace %} diff --git a/hawc/apps/animal/templates/animal/experiment_copy_selector.html b/hawc/apps/animal/templates/animal/experiment_copy_selector.html index 52905a3fec..c31651fb8e 100644 --- a/hawc/apps/animal/templates/animal/experiment_copy_selector.html +++ b/hawc/apps/animal/templates/animal/experiment_copy_selector.html @@ -1,11 +1,6 @@ {% extends 'study/study_detail.html' %} {% load add_class %} -{% load selectable_tags %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} {% block content %} diff --git a/hawc/apps/animal/templates/animal/experiment_form.html b/hawc/apps/animal/templates/animal/experiment_form.html index d6a4e23ad9..dead0bc2d2 100644 --- a/hawc/apps/animal/templates/animal/experiment_form.html +++ b/hawc/apps/animal/templates/animal/experiment_form.html @@ -1,7 +1,6 @@ {% extends 'assessment-rooted.html' %} {% load crispy_forms_tags %} -{% load selectable_tags %} {% block content %} {% crispy form %} @@ -45,12 +44,14 @@ // handle all logic related to dtxid + cas + chemical fields var handleChemicalPropertiesInformation = function(){ // if `dtxsid` is selected, change casrn/chemical_name - $('input[name=dtxsid_0]').bind('djselectableselect', function (event, item) { - if (item.item.casrn) { - $('#id_cas').val(item.item.casrn); + $('select[name=dtxsid]').on('select2:select', function (event) { + let casrn = event.params.data.casrn, + chemical_name = event.params.data.chemical_name; + if (casrn) { + $('#id_cas').append(new Option(casrn, casrn, false, true)); } - if (item.item.chemical_name) { - $('#id_chemical').val(item.item.chemical_name); + if (chemical_name) { + $('#id_chemical').append(new Option(chemical_name, chemical_name, false, true)); } }); diff --git a/hawc/apps/assessment/autocomplete.py b/hawc/apps/assessment/autocomplete.py new file mode 100644 index 0000000000..2057d5d6a9 --- /dev/null +++ b/hawc/apps/assessment/autocomplete.py @@ -0,0 +1,56 @@ +from ..common.autocomplete import BaseAutocomplete, register +from . import models + + +@register +class AssessmentAutocomplete(BaseAutocomplete): + model = models.Assessment + search_fields = ["name"] + + +@register +class DSSToxAutocomplete(BaseAutocomplete): + model = models.DSSTox + search_fields = ["dtxsid", "content__preferredName", "content__casrn"] + + def get_result(self, obj): + result = super().get_result(obj) + result.update(casrn=obj.content["casrn"], chemical_name=obj.content["preferredName"]) + return result + + def get_result_label(self, result): + return result.verbose_str + + +@register +class EffectTagAutocomplete(BaseAutocomplete): + model = models.EffectTag + search_fields = ["name"] + + +@register +class SpeciesAutocomplete(BaseAutocomplete): + model = models.Species + search_fields = ["name"] + filter_fields = ["animalgroup__experiment__study__assessment_id"] + + @classmethod + def get_base_queryset(cls, filters: dict = None): + return super().get_base_queryset(filters).distinct() + + +@register +class StrainAutocomplete(BaseAutocomplete): + model = models.Strain + search_fields = ["name"] + filter_fields = ["animalgroup__experiment__study__assessment_id"] + + @classmethod + def get_base_queryset(cls, filters: dict = None): + return super().get_base_queryset(filters).distinct() + + +@register +class DoseUnitsAutocomplete(BaseAutocomplete): + model = models.DoseUnits + search_fields = ["name"] diff --git a/hawc/apps/assessment/forms.py b/hawc/apps/assessment/forms.py index 0523aaa230..838283186b 100644 --- a/hawc/apps/assessment/forms.py +++ b/hawc/apps/assessment/forms.py @@ -14,6 +14,7 @@ from hawc.services.epa.dsstox import DssSubstance +from ..common.autocomplete import AutocompleteMultipleChoiceField, AutocompleteTextWidget from ..common.forms import ( BaseFormHelper, QuillField, @@ -21,11 +22,10 @@ form_actions_create_or_close, ) from ..common.helper import new_window_a, tryParseInt -from ..common.selectable import AutoCompleteSelectMultipleWidget, AutoCompleteWidget from ..common.widgets import DateCheckboxInput -from ..myuser.lookups import HAWCUserLookup +from ..myuser.autocomplete import UserAutocomplete from ..myuser.models import HAWCUser -from . import lookups, models +from . import autocomplete, models class AssessmentForm(forms.ModelForm): @@ -65,18 +65,13 @@ def __init__(self, *args, **kwargs): self.fields["project_manager"].initial = [self.user] self.fields["year"].initial = timezone.now().year - self.fields["dtxsids"].widget = AutoCompleteSelectMultipleWidget( - lookup_class=lookups.DssToxIdLookup - ) - self.fields["project_manager"].widget = AutoCompleteSelectMultipleWidget( - lookup_class=HAWCUserLookup - ) - self.fields["team_members"].widget = AutoCompleteSelectMultipleWidget( - lookup_class=HAWCUserLookup - ) - self.fields["reviewers"].widget = AutoCompleteSelectMultipleWidget( - lookup_class=HAWCUserLookup + self.fields["dtxsids"] = AutocompleteMultipleChoiceField( + autocomplete_class=autocomplete.DSSToxAutocomplete ) + for field in ["project_manager", "team_members", "reviewers"]: + self.fields[field] = AutocompleteMultipleChoiceField( + autocomplete_class=UserAutocomplete + ) if not settings.PM_CAN_MAKE_PUBLIC: help_text = " Contact the HAWC team to change." @@ -297,13 +292,15 @@ class DoseUnitsForm(forms.ModelForm): class Meta: model = models.DoseUnits fields = "__all__" + widgets = { + "name": AutocompleteTextWidget( + autocomplete_class=autocomplete.DoseUnitsAutocomplete, field="name" + ) + } def __init__(self, *args, **kwargs): kwargs.pop("parent", None) super().__init__(*args, **kwargs) - self.fields["name"].widget = AutoCompleteWidget( - lookup_class=lookups.DoseUnitsLookup, allow_new=True - ) @property def helper(self): @@ -351,13 +348,15 @@ class EffectTagForm(forms.ModelForm): class Meta: model = models.EffectTag fields = "__all__" + widgets = { + "name": AutocompleteTextWidget( + autocomplete_class=autocomplete.EffectTagAutocomplete, field="name" + ) + } def __init__(self, *args, **kwargs): kwargs.pop("parent") super().__init__(*args, **kwargs) - self.fields["name"].widget = AutoCompleteWidget( - lookup_class=lookups.EffectTagLookup, allow_new=True - ) @property def helper(self): diff --git a/hawc/apps/assessment/lookups.py b/hawc/apps/assessment/lookups.py deleted file mode 100644 index ef028d9d27..0000000000 --- a/hawc/apps/assessment/lookups.py +++ /dev/null @@ -1,63 +0,0 @@ -from selectable.base import ModelLookup -from selectable.registry import registry - -from . import models - - -class DssToxIdLookup(ModelLookup): - model = models.DSSTox - search_fields = ( - "dtxsid__icontains", - "content__preferredName__icontains", - "content__casrn__icontains", - ) - - def get_item_label(self, obj): - return obj.verbose_str - - def get_item_value(self, obj): - return obj.verbose_str - - def format_item(self, item): - result = super().format_item(item) - result.update(casrn=item.content["casrn"], chemical_name=item.content["preferredName"]) - return result - - -class AssessmentLookup(ModelLookup): - model = models.Assessment - search_fields = ("name__icontains",) - - -class SpeciesLookup(ModelLookup): - model = models.Species - search_fields = ("name__icontains",) - - -class StrainLookup(ModelLookup): - model = models.Strain - search_fields = ("name__icontains",) - - -class DoseUnitsLookup(ModelLookup): - model = models.DoseUnits - search_fields = ("name__icontains",) - - -class BaseEndpointLookup(ModelLookup): - model = models.BaseEndpoint - search_fields = ("name__icontains",) - - -class EffectTagLookup(ModelLookup): - model = models.EffectTag - search_fields = ("name__icontains",) - - -registry.register(DssToxIdLookup) -registry.register(AssessmentLookup) -registry.register(SpeciesLookup) -registry.register(DoseUnitsLookup) -registry.register(StrainLookup) -registry.register(EffectTagLookup) -registry.register(BaseEndpointLookup) diff --git a/hawc/apps/assessment/templates/assessment/assessment_create_form.html b/hawc/apps/assessment/templates/assessment/assessment_create_form.html index 6f7066c448..da6503099f 100644 --- a/hawc/apps/assessment/templates/assessment/assessment_create_form.html +++ b/hawc/apps/assessment/templates/assessment/assessment_create_form.html @@ -1,12 +1,7 @@ {% extends 'crumbless.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %} {% crispy form %} {% endblock %} diff --git a/hawc/apps/assessment/templates/assessment/assessment_form.html b/hawc/apps/assessment/templates/assessment/assessment_form.html index 41bbe2452b..74e116d965 100644 --- a/hawc/apps/assessment/templates/assessment/assessment_form.html +++ b/hawc/apps/assessment/templates/assessment/assessment_form.html @@ -1,12 +1,7 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %} {% crispy form %} {% endblock %} diff --git a/hawc/apps/assessment/templates/assessment/doseunits_form.html b/hawc/apps/assessment/templates/assessment/doseunits_form.html index f881046a7d..1e9e4f3367 100644 --- a/hawc/apps/assessment/templates/assessment/doseunits_form.html +++ b/hawc/apps/assessment/templates/assessment/doseunits_form.html @@ -1,6 +1,5 @@ {% extends 'crumbless.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} {% block content %} diff --git a/hawc/apps/assessment/templates/assessment/effecttag_form.html b/hawc/apps/assessment/templates/assessment/effecttag_form.html index b296097811..6fc11ef57e 100644 --- a/hawc/apps/assessment/templates/assessment/effecttag_form.html +++ b/hawc/apps/assessment/templates/assessment/effecttag_form.html @@ -1,12 +1,7 @@ {% extends 'crumbless.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %} {% crispy form %} {% endblock content %} @@ -14,8 +9,8 @@ {% block extrajs %} diff --git a/hawc/apps/bmd/templates/bmd/session_form.html b/hawc/apps/bmd/templates/bmd/session_form.html index b84acd42f0..f39a0dcec0 100644 --- a/hawc/apps/bmd/templates/bmd/session_form.html +++ b/hawc/apps/bmd/templates/bmd/session_form.html @@ -1,12 +1,7 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %} Cancel

{{object}}

diff --git a/hawc/apps/common/apps.py b/hawc/apps/common/apps.py new file mode 100644 index 0000000000..5fa8b43f12 --- /dev/null +++ b/hawc/apps/common/apps.py @@ -0,0 +1,10 @@ +from django.apps import AppConfig + + +class CommonConfig(AppConfig): + name = "hawc.apps.common" + + def ready(self): + from . import autocomplete + + autocomplete.autodiscover() diff --git a/hawc/apps/common/autocomplete/__init__.py b/hawc/apps/common/autocomplete/__init__.py new file mode 100644 index 0000000000..a49739368a --- /dev/null +++ b/hawc/apps/common/autocomplete/__init__.py @@ -0,0 +1,22 @@ +from .forms import ( + AutocompleteChoiceField, + AutocompleteMultipleChoiceField, + AutocompleteSelectMultipleWidget, + AutocompleteSelectWidget, + AutocompleteTextWidget, +) +from .registry import autodiscover, get_autocomplete, register +from .views import BaseAutocomplete, SearchLabelMixin + +__all__ = [ + "AutocompleteChoiceField", + "AutocompleteMultipleChoiceField", + "AutocompleteSelectMultipleWidget", + "AutocompleteSelectWidget", + "AutocompleteTextWidget", + "BaseAutocomplete", + "SearchLabelMixin", + "autodiscover", + "get_autocomplete", + "register", +] diff --git a/hawc/apps/common/autocomplete/forms.py b/hawc/apps/common/autocomplete/forms.py new file mode 100644 index 0000000000..5f7c7cb3ac --- /dev/null +++ b/hawc/apps/common/autocomplete/forms.py @@ -0,0 +1,133 @@ +from typing import Optional, Type + +from dal import autocomplete +from dal_select2.widgets import I18N_PATH +from django.conf import settings +from django.forms import Media, ModelChoiceField, ModelMultipleChoiceField + +from .views import BaseAutocomplete + + +class AutocompleteWidgetMixin: + @property + def media(self): + """Return JS/CSS resources for the widget.""" + extra = "" if settings.DEBUG else ".min" + i18n_name = self._get_language_code() + i18n_file = (f"{I18N_PATH}{i18n_name}.js",) if i18n_name else () + + return Media( + js=( + "admin/js/vendor/select2/select2.full.js", + f"{settings.STATIC_URL}patched/dal/3.9.4/js/autocomplete_light.js", + f"autocomplete_light/select2{extra}.js", + f"{settings.STATIC_URL}patched/dal/3.9.4/js/select2text.js", + ) + + i18n_file, + css={ + "screen": ( + f"admin/css/vendor/select2/select2{extra}.css", + "admin/css/autocomplete.css", + "autocomplete_light/select2.css", + f"{settings.STATIC_URL}patched/dal/3.9.4/css/select2-bootstrap.css", + ), + }, + ) + + def __init__( + self, + autocomplete_class: Type[BaseAutocomplete], + filters: Optional[dict] = None, + autocomplete_function: Optional[str] = None, + *args, + **kwargs, + ): + # set url + filters = filters or {} + kwargs["url"] = autocomplete_class.url(**filters) + + # add bootstrap theme to attrs + attrs = kwargs.get("attrs", {}) + attrs.setdefault("data-theme", "bootstrap") + attrs.setdefault("data-width", "100%") + kwargs["attrs"] = attrs + + super().__init__(*args, **kwargs) + + self.filters = filters + self.autocomplete_class = autocomplete_class + self.autocomplete_function = autocomplete_function or self.autocomplete_function + + def set_filters(self, filters: dict): + self.filters = filters + self.url = self.autocomplete_class.url(**self.filters) + + def update_filters(self, filters: dict): + self.filters.update(filters) + self.url = self.autocomplete_class.url(**self.filters) + + +class AutocompleteSelectWidget(AutocompleteWidgetMixin, autocomplete.ModelSelect2): + pass + + +class AutocompleteSelectMultipleWidget(AutocompleteWidgetMixin, autocomplete.ModelSelect2Multiple): + def __init__(self, *args, **kwargs): + attrs = kwargs.get("attrs", {}) + # remove ability to delete all selections at once + attrs.setdefault("data-allow-clear", "false") + kwargs["attrs"] = attrs + super().__init__(*args, **kwargs) + + +class AutocompleteTextWidget(AutocompleteWidgetMixin, autocomplete.Select2): + autocomplete_function = "select2text" + + def __init__(self, field: str, *args, **kwargs): + # add field to filters + filters = kwargs.get("filters", {}) + filters.setdefault("field", field) + kwargs["filters"] = filters + super().__init__(*args, **kwargs) + + def filter_choices_to_render(self, selected_choices): + self.choices = [[choice, choice] for choice in selected_choices] + + +class AutocompleteFieldMixin: + def __init__( + self, + autocomplete_class: Type[BaseAutocomplete], + filters: Optional[dict] = None, + *args, + **kwargs, + ): + self.autocomplete_class = autocomplete_class + filters = filters or {} + kwargs["queryset"] = autocomplete_class.get_base_queryset(filters) + + # use the same labels for initial values that are used by autocomplete + self.label_from_instance = autocomplete_class().get_result_label + + # determine the widget to use + if isinstance(self, ModelMultipleChoiceField): + Widget = AutocompleteSelectMultipleWidget + elif isinstance(self, ModelChoiceField): + Widget = AutocompleteSelectWidget + else: + raise NotImplementedError() + kwargs["widget"] = Widget(autocomplete_class=autocomplete_class, filters=filters) + + return super().__init__(*args, **kwargs) + + def set_filters(self, filters: dict): + self.queryset = self.autocomplete_class.get_base_queryset(filters) + self.widget.set_filters(filters) + + +class AutocompleteChoiceField(AutocompleteFieldMixin, ModelChoiceField): + pass + + +class AutocompleteMultipleChoiceField(AutocompleteFieldMixin, ModelMultipleChoiceField): + pass diff --git a/hawc/apps/common/autocomplete/registry.py b/hawc/apps/common/autocomplete/registry.py new file mode 100644 index 0000000000..6ffd5e06e9 --- /dev/null +++ b/hawc/apps/common/autocomplete/registry.py @@ -0,0 +1,56 @@ +from typing import Type + +from django.http import Http404 +from django.utils.encoding import force_str +from django.utils.module_loading import autodiscover_modules + +from .views import BaseAutocomplete + + +class AutocompleteRegistry: + def __init__(self): + self._registry: dict[str, Type[BaseAutocomplete]] = {} + + def validate(self, lookup): + if not issubclass(lookup, BaseAutocomplete): + raise ValueError("Must inherit from the BaseAutocomplete class") + + def register(self, lookup): + self.validate(lookup) + key = force_str(lookup.registry_key()) + if key in self._registry: + raise KeyError(f"The key {key} is already registered") + self._registry[key] = lookup + + def unregister(self, lookup): + self.validate(lookup) + key = force_str(lookup.registry_key()) + if key not in self._registry: + raise KeyError(f"The key {key} is not registered") + del self._registry[key] + + def get(self, key) -> Type[BaseAutocomplete]: + try: + return self._registry[key] + except KeyError: + raise ValueError(f"Key not found: {key}") + + +registry = AutocompleteRegistry() + + +def register(Cls): + registry.register(Cls) + return Cls + + +def get_autocomplete(request, autocomplete_name): + try: + autocomplete_cls = registry.get(autocomplete_name) + except ValueError: + raise Http404(f"Autocomplete {autocomplete_name} not found") + return autocomplete_cls.as_view()(request) + + +def autodiscover(): + autodiscover_modules("autocomplete", register_to=registry) diff --git a/hawc/apps/common/autocomplete/views.py b/hawc/apps/common/autocomplete/views.py new file mode 100644 index 0000000000..059b4b2da7 --- /dev/null +++ b/hawc/apps/common/autocomplete/views.py @@ -0,0 +1,111 @@ +from typing import Optional + +from dal import autocomplete +from django.http import HttpResponseForbidden + +from ..helper import reverse_with_query_lazy + + +class BaseAutocomplete(autocomplete.Select2QuerySetView): + filter_fields: list[str] = [] + order_by: str = "" + order_direction: str = "" + paginate_by: int = 30 + + def get_field(self, obj): + return getattr(obj, self.field) + + def get_field_result(self, obj): + return { + "id": self.get_result_value(obj), + "text": self.get_field(obj), + "selected_text": self.get_field(obj), + } + + def get_result(self, obj): + return { + "id": self.get_result_value(obj), + "text": self.get_result_label(obj), + "selected_text": self.get_selected_result_label(obj), + } + + def get_results(self, context): + return [ + self.get_field_result(obj) if self.field else self.get_result(obj) + for obj in context["object_list"] + ] + + def update_qs(self, qs): + return qs + + @classmethod + def get_base_queryset(cls, filters: Optional[dict] = None): + """ + Gets the base queryset to perform searches on + + Args: + filters (dict, optional): Field/value pairings to filter queryset on, as long as fields are in class property filter_fields + + Returns: + QuerySet: Base queryset + """ + filters = filters or {} + filters = {key: filters[key] for key in filters if key in cls.filter_fields} + qs = cls.model.objects.all() + return qs.filter(**filters) + + def get_queryset(self): + # get base queryset + qs = self.get_base_queryset(self.request.GET) + + # check forwarded values for search_fields + self.search_fields = self.forwarded.get("search_fields") or self.search_fields + + # perform search + qs = self.get_search_results(qs, self.q) + + if self.field: + # order by field and get distinct + qs = qs.order_by(self.field).distinct(self.field) + else: + # check forwarded values for ordering + self.order_by = self.forwarded.get("order_by") or self.order_by + self.order_direction = self.forwarded.get("order_direction") or self.order_direction + if self.order_by: + qs = qs.order_by(self.order_direction + self.order_by) + + 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] + return super().dispatch(request, *args, **kwargs) + + @classmethod + def registry_key(cls): + app_name = cls.__module__.split(".")[-2].lower() + class_name = cls.__name__.lower() + return f"{app_name}-{class_name}" + + @classmethod + def url(cls, **kwargs): + # must lazily reverse url to prevent circular imports + return reverse_with_query_lazy("autocomplete", args=[cls.registry_key()], query=kwargs) + + +class SearchLabelMixin: + """ + Constructs autocomplete labels by using values from the search_fields + """ + + def get_result_label(self, result): + labels = [] + for path in self.search_fields: + item = result + for attribute in path.split("__"): + item = getattr(item, attribute) + labels.append(str(item)) + return " | ".join(labels) diff --git a/hawc/apps/common/forms.py b/hawc/apps/common/forms.py index f0b89a506c..f5ad440f3e 100644 --- a/hawc/apps/common/forms.py +++ b/hawc/apps/common/forms.py @@ -7,7 +7,7 @@ from django import forms from django.template.loader import render_to_string -from . import selectable, validators, widgets +from . import autocomplete, validators, widgets ASSESSMENT_UNIQUE_MESSAGE = "Must be unique for assessment (current value already exists)." @@ -119,11 +119,12 @@ def add_refresh_page_note(self): class CopyAsNewSelectorForm(forms.Form): label = None - lookup_class = None + parent_field = None + autocomplete_class = None def __init__(self, *args, **kwargs): parent_id = kwargs.pop("parent_id") - super(CopyAsNewSelectorForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.setupSelector(parent_id) @property @@ -131,13 +132,11 @@ def helper(self): return BaseFormHelper(self) def setupSelector(self, parent_id): - fld = selectable.AutoCompleteSelectField( - lookup_class=self.lookup_class, - allow_new=False, - label=self.label, - widget=selectable.AutoComboboxSelectWidget, + filters = {self.parent_field: parent_id} + fld = autocomplete.AutocompleteChoiceField( + autocomplete_class=self.autocomplete_class, filters=filters, label=self.label ) - fld.widget.update_query_parameters({"related": parent_id}) + fld.widget.forward = ["search_fields", "order_by", "order_direction"] fld.widget.attrs["class"] = "col-md-10" self.fields["selector"] = fld diff --git a/hawc/apps/common/helper.py b/hawc/apps/common/helper.py index 02b39cd5a6..ba9968f448 100644 --- a/hawc/apps/common/helper.py +++ b/hawc/apps/common/helper.py @@ -14,8 +14,11 @@ from django.core.serializers.json import DjangoJSONEncoder from django.db.models import QuerySet from django.http import QueryDict +from django.urls import reverse from django.utils.encoding import force_str +from django.utils.functional import lazy from django.utils.html import strip_tags +from django.utils.http import urlencode from docx.document import Document from matplotlib.axes import Axes from matplotlib.dates import DateFormatter @@ -404,6 +407,27 @@ def event_plot(series: pd.Series) -> Axes: return ax +def reverse_with_query(*args, query: dict, **kwargs): + """ + Performs Django's `reverse` and appends a query string. + + Args: + *args: Arguments for Django's `reverse` + **kwargs: Named arguments for Django's `reverse` + query (dict): Dictionary to build query string from + + Returns: + str: reversed url with query string + """ + url = reverse(*args, **kwargs) + query = urlencode(query) + query = f"?{query}" if query else query + return url + query + + +reverse_with_query_lazy = lazy(reverse_with_query, str) + + class PydanticToDjangoError: """ Context manager to catch pydantic errors and return an appropriate Django/DRF error. diff --git a/hawc/apps/common/lookups.py b/hawc/apps/common/lookups.py deleted file mode 100644 index 3d4df43a86..0000000000 --- a/hawc/apps/common/lookups.py +++ /dev/null @@ -1,116 +0,0 @@ -import operator -from functools import reduce -from typing import Any, Set - -from django.db.models import Q -from django.forms import ValidationError -from selectable.base import ModelLookup - -from .helper import tryParseInt - - -class DistinctStringLookup(ModelLookup): - """ - Return distinct strings for a single CharField in a model - """ - - distinct_field = None - - def get_query(self, request, term): - return ( - self.get_queryset() - .filter(**{self.distinct_field + "__icontains": term}) - .order_by(self.distinct_field) - .distinct(self.distinct_field) - ) - - def get_item_value(self, item): - return getattr(item, self.distinct_field) - - def get_item_label(self, item): - return self.get_item_value(item) - - -class RelatedLookup(ModelLookup): - """ - Perform a search where related_filter is required, and search fields are - ORd together. Ex: - - WHERE (self.related_filter = related_id) AND - ( ... OR ... OR ...) for search fields - """ - - related_filter = None # filter-string - - def get_search_filter(self, request, term): - return [Q(**{field: term}) for field in self.search_fields] - - def get_query(self, request, term): - id_ = tryParseInt(request.GET.get("related"), -1) - qs = self.get_queryset() - search_fields = self.get_search_filter(request, term) - return qs.filter(Q(**{self.related_filter: id_}) & reduce(operator.or_, search_fields)) - - -class RelatedDistinctStringLookup(DistinctStringLookup): - related_filter = None - - def get_query(self, request, term): - qs = super().get_query(request, term) - id_ = tryParseInt(request.GET.get("related"), -1) - - return qs.filter(Q(**{self.related_filter: id_})) - - -class UserSpecifiedRelatedLookup(RelatedLookup): - search_fields = None # user choices below instead - search_fields_choices: Set = set() - order_by_choices: Set = set() - - def get_search_filter(self, request, term): - """Return a valid search filter, from the available choices""" - search_fields = request.GET.get("search_fields", "").split(",") - if any(field not in self.search_fields_choices for field in search_fields): - raise ValidationError("Invalid search fields specified") - if not search_fields: - raise ValidationError("At least one search field is required") - - # TODO - can we do this without binding request to self? - request._search_fields = search_fields - self._current_request = request - fields = [f"{field}__icontains" for field in search_fields] - return [Q(**{field: term}) for field in fields] - - def get_order_by(self, request): - """Return a valid ordering column, from available choices""" - order_by = request.GET.get("order_by", "id") - test_value = order_by.removeprefix("-") # check for both forward/reverse orders - if test_value not in self.order_by_choices: - raise ValidationError(f"Invalid order_by: {order_by}") - return order_by - - def get_query(self, request, term): - try: - order_by = self.get_order_by(request) - return super().get_query(request, term).distinct().order_by(order_by) - except ValidationError: - return super().get_queryset().none() - - def get_item_label(self, obj): - return " | ".join( - [self._get_field_label(obj, field) for field in self._current_request._search_fields] - ) - - def get_item_value(self, obj): - return self.get_item_label(obj) - - def _get_field_label(self, obj: Any, path: str) -> str: - """Return label for a string-based django ORM attribute path. - - For example, `related_item__some_field__foo` will return a str based representation - of `obj.related_item.some_field.foo`. - """ - item = obj - for attribute in path.split("__"): - item = getattr(item, attribute) - return str(item) diff --git a/hawc/apps/common/selectable.py b/hawc/apps/common/selectable.py deleted file mode 100644 index 43e40ed801..0000000000 --- a/hawc/apps/common/selectable.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.conf import settings -from selectable.forms import * # noqa -from selectable.forms.widgets import SelectableMediaMixin - -SelectableMediaMixin.Media.js = ( - *SelectableMediaMixin.Media.js, - settings.STATIC_URL + "js/selectable_bootstrap.js", -) diff --git a/hawc/apps/common/templates/common/htmx_autocomplete.html b/hawc/apps/common/templates/common/htmx_autocomplete.html new file mode 100644 index 0000000000..8946f4594f --- /dev/null +++ b/hawc/apps/common/templates/common/htmx_autocomplete.html @@ -0,0 +1,5 @@ +{% load static %} + + + + diff --git a/hawc/apps/epi/autocomplete.py b/hawc/apps/epi/autocomplete.py new file mode 100644 index 0000000000..fc962b8322 --- /dev/null +++ b/hawc/apps/epi/autocomplete.py @@ -0,0 +1,58 @@ +from ..common.autocomplete import BaseAutocomplete, register +from . import models + + +@register +class CriteriaAutocomplete(BaseAutocomplete): + model = models.Criteria + search_fields = ["description"] + filter_fields = ["assessment_id"] + + +@register +class StudyPopulationAutocomplete(BaseAutocomplete): + model = models.StudyPopulation + search_fields = ["name"] + filter_fields = ["study_id", "study__assessment_id"] + + +@register +class ExposureAutocomplete(BaseAutocomplete): + model = models.Exposure + search_fields = ["name"] + filter_fields = ["study_population_id", "study_population__study__assessment_id"] + + +@register +class OutcomeAutocomplete(BaseAutocomplete): + model = models.Outcome + search_fields = ["name"] + filter_fields = ["study_population_id", "study_population__study__assessment_id"] + + +@register +class ComparisonSetAutocomplete(BaseAutocomplete): + model = models.ComparisonSet + search_fields = ["name"] + filter_fields = ["study_population_id", "outcome_id"] + + +@register +class AdjustmentFactorAutocomplete(BaseAutocomplete): + model = models.AdjustmentFactor + search_fields = ["description"] + filter_fields = ["assessment_id"] + + +@register +class ResultAutocomplete(BaseAutocomplete): + model = models.Result + search_fields = ["metric__metric", "comparison_set__name"] + filter_fields = ["outcome_id"] + + +@register +class CountryAutocomplete(BaseAutocomplete): + model = models.Country + search_fields = ["name"] + filter_fields = ["studypopulation__study__assessment_id"] diff --git a/hawc/apps/epi/forms.py b/hawc/apps/epi/forms.py index 4a2db0d77c..b8252ad84c 100644 --- a/hawc/apps/epi/forms.py +++ b/hawc/apps/epi/forms.py @@ -6,9 +6,14 @@ from django.forms.models import BaseModelFormSet, modelformset_factory from django.urls import reverse -from ..assessment.lookups import BaseEndpointLookup, DssToxIdLookup, EffectTagLookup +from ..assessment.autocomplete import DSSToxAutocomplete, EffectTagAutocomplete from ..assessment.models import DoseUnits -from ..common import selectable +from ..common.autocomplete import ( + AutocompleteMultipleChoiceField, + AutocompleteSelectMultipleWidget, + AutocompleteSelectWidget, + AutocompleteTextWidget, +) from ..common.forms import ( ASSESSMENT_UNIQUE_MESSAGE, BaseFormHelper, @@ -17,8 +22,8 @@ form_actions_create_or_close, ) from ..common.helper import tryParseInt -from ..study.lookups import EpiStudyLookup -from . import constants, lookups, models +from ..study.autocomplete import StudyAutocomplete +from . import autocomplete, constants, models class CriteriaForm(forms.ModelForm): @@ -38,12 +43,11 @@ class Meta: def __init__(self, *args, **kwargs): assessment = kwargs.pop("parent", None) super().__init__(*args, **kwargs) - self.fields["description"].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.CriteriaLookup, allow_new=True - ) self.instance.assessment = assessment - self.fields["description"].widget.update_query_parameters( - {"related": self.instance.assessment_id} + self.fields["description"].widget = AutocompleteTextWidget( + autocomplete_class=autocomplete.CriteriaAutocomplete, + field="description", + filters={"assessment_id": assessment.id}, ) def clean(self): @@ -91,41 +95,44 @@ class StudyPopulationForm(forms.ModelForm): "confounding_criteria": "C", } - inclusion_criteria = selectable.AutoCompleteSelectMultipleField( - lookup_class=lookups.CriteriaLookup, required=False + inclusion_criteria = AutocompleteMultipleChoiceField( + autocomplete_class=autocomplete.CriteriaAutocomplete, required=False ) - exclusion_criteria = selectable.AutoCompleteSelectMultipleField( - lookup_class=lookups.CriteriaLookup, required=False + exclusion_criteria = AutocompleteMultipleChoiceField( + autocomplete_class=autocomplete.CriteriaAutocomplete, required=False ) - confounding_criteria = selectable.AutoCompleteSelectMultipleField( - lookup_class=lookups.CriteriaLookup, required=False + confounding_criteria = AutocompleteMultipleChoiceField( + autocomplete_class=autocomplete.CriteriaAutocomplete, required=False ) class Meta: model = models.StudyPopulation exclude = ("study", "criteria") labels = {"comments": "Recruitment description"} + widgets = { + "region": AutocompleteTextWidget( + autocomplete_class=autocomplete.StudyPopulationAutocomplete, field="region" + ), + "state": AutocompleteTextWidget( + autocomplete_class=autocomplete.StudyPopulationAutocomplete, field="state" + ), + "countries": AutocompleteSelectMultipleWidget( + autocomplete_class=autocomplete.CountryAutocomplete + ), + } def __init__(self, *args, **kwargs): study = kwargs.pop("parent", None) super().__init__(*args, **kwargs) self.fields["countries"].required = True self.fields["comments"] = self.fields.pop("comments") # move to end - self.fields["region"].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.RegionLookup, allow_new=True - ) - self.fields["state"].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.StateLookup, allow_new=True - ) if study: self.instance.study = study for fld in self.CRITERION_FIELDS: - self.fields[fld].widget.update_query_parameters( - {"related": self.instance.study.assessment_id} - ) + self.fields[fld].set_filters({"assessment_id": self.instance.study.assessment_id}) if self.instance.id: self.fields[fld].initial = getattr(self.instance, fld) @@ -203,7 +210,8 @@ def clean(self): class StudyPopulationSelectorForm(CopyAsNewSelectorForm): label = "Study Population" - lookup_class = lookups.StudyPopulationByStudyLookup + parent_field = "study_id" + autocomplete_class = autocomplete.StudyPopulationAutocomplete class AdjustmentFactorForm(forms.ModelForm): @@ -223,12 +231,11 @@ class Meta: def __init__(self, *args, **kwargs): assessment = kwargs.pop("parent", None) super().__init__(*args, **kwargs) - self.fields["description"].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.AdjustmentFactorLookup, allow_new=True - ) self.instance.assessment = assessment - self.fields["description"].widget.update_query_parameters( - {"related": self.instance.assessment_id} + self.fields["description"].widget = AutocompleteTextWidget( + autocomplete_class=autocomplete.AdjustmentFactorAutocomplete, + field="description", + filters={"assessment_id": assessment.id}, ) def clean(self): @@ -264,22 +271,20 @@ class ExposureForm(forms.ModelForm): class Meta: model = models.Exposure exclude = ("study_population",) + widgets = {"dtxsid": AutocompleteSelectWidget(autocomplete_class=DSSToxAutocomplete)} def __init__(self, *args, **kwargs): study_population = kwargs.pop("parent", None) super().__init__(*args, **kwargs) - self.fields["dtxsid"].widget = selectable.AutoCompleteSelectWidget( - lookup_class=DssToxIdLookup + self.fields["measured"].widget = AutocompleteTextWidget( + autocomplete_class=autocomplete.ExposureAutocomplete, field="measured" ) - self.fields["measured"].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.ExposureMeasuredLookup, allow_new=True + self.fields["metric"].widget = AutocompleteTextWidget( + autocomplete_class=autocomplete.ExposureAutocomplete, field="metric" ) - self.fields["metric"].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.ExposureMetricLookup, allow_new=True - ) - self.fields["age_of_exposure"].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.AgeOfExposureLookup, allow_new=True + self.fields["age_of_exposure"].widget = AutocompleteTextWidget( + autocomplete_class=autocomplete.ExposureAutocomplete, field="age_of_exposure" ) if study_population: @@ -334,7 +339,8 @@ def helper(self): class ExposureSelectorForm(CopyAsNewSelectorForm): label = "Exposure" - lookup_class = lookups.ExposureByStudyPopulationLookup + parent_field = "study_population_id" + autocomplete_class = autocomplete.ExposureAutocomplete class OutcomeForm(forms.ModelForm): @@ -351,28 +357,28 @@ class Meta: model = models.Outcome exclude = ("assessment", "study_population") labels = {"summary": "Comments"} + widgets = { + "effects": AutocompleteSelectMultipleWidget(autocomplete_class=EffectTagAutocomplete) + } def __init__(self, *args, **kwargs): assessment = kwargs.pop("assessment", None) study_population = kwargs.pop("parent", None) super().__init__(*args, **kwargs) - self.fields["name"].widget = selectable.AutoCompleteWidget( - lookup_class=BaseEndpointLookup, allow_new=True + self.fields["name"].widget = AutocompleteTextWidget( + autocomplete_class=autocomplete.OutcomeAutocomplete, field="name" ) - self.fields["system"].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.SystemLookup, allow_new=True + self.fields["system"].widget = AutocompleteTextWidget( + autocomplete_class=autocomplete.OutcomeAutocomplete, field="system" ) - self.fields["effect"].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.EffectLookup, allow_new=True + self.fields["effect"].widget = AutocompleteTextWidget( + autocomplete_class=autocomplete.OutcomeAutocomplete, field="effect" ) - self.fields["effect_subtype"].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.EffectSubtypeLookup, allow_new=True + self.fields["effect_subtype"].widget = AutocompleteTextWidget( + autocomplete_class=autocomplete.OutcomeAutocomplete, field="effect_subtype" ) - self.fields["age_of_measurement"].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.AgeOfMeasurementLookup, allow_new=True - ) - self.fields["effects"].widget = selectable.AutoCompleteSelectMultipleWidget( - lookup_class=EffectTagLookup + self.fields["age_of_measurement"].widget = AutocompleteTextWidget( + autocomplete_class=autocomplete.OutcomeAutocomplete, field="age_of_measurement" ) if assessment: self.instance.assessment = assessment @@ -423,51 +429,63 @@ class OutcomeFilterForm(forms.Form): ("diagnostic", "diagnostic"), ) - studies = selectable.AutoCompleteSelectMultipleField( + studies = AutocompleteMultipleChoiceField( + autocomplete_class=StudyAutocomplete, label="Study reference", - lookup_class=EpiStudyLookup, help_text="ex: Smith et al. 2010", required=False, ) name = forms.CharField( label="Outcome name", - widget=selectable.AutoCompleteWidget(lookups.OutcomeLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.OutcomeAutocomplete, field="name" + ), help_text="ex: blood, glucose", required=False, ) study_population = forms.CharField( label="Study population", - widget=selectable.AutoCompleteWidget(lookups.StudyPopulationByAssessmentLookup), + 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=selectable.AutoCompleteWidget(lookups.RelatedExposureMetricLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.ExposureAutocomplete, field="metric" + ), help_text="ex: drinking water", required=False, ) age_profile = forms.CharField( label="Age profile", - widget=selectable.AutoCompleteWidget(lookups.RelatedStudyPopulationAgeProfileLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.StudyPopulationAutocomplete, field="age_profile" + ), help_text="ex: children", required=False, ) source = forms.CharField( label="Study population source", - widget=selectable.AutoCompleteWidget(lookups.RelatedStudyPopulationSourceLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.StudyPopulationAutocomplete, field="source" + ), help_text="ex: occupational exposure", required=False, ) country = forms.CharField( label="Study population country", - widget=selectable.AutoCompleteWidget(lookups.RelatedCountryNameLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.CountryAutocomplete, field="name" + ), help_text="ex: Japan", required=False, ) @@ -482,21 +500,27 @@ class OutcomeFilterForm(forms.Form): system = forms.CharField( label="System", - widget=selectable.AutoCompleteWidget(lookups.RelatedSystemLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.OutcomeAutocomplete, field="system" + ), help_text="ex: immune and lymphatic system", required=False, ) effect = forms.CharField( label="Effect", - widget=selectable.AutoCompleteWidget(lookups.RelatedEffectLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.OutcomeAutocomplete, field="effect" + ), help_text="ex: Cancer", required=False, ) effect_subtype = forms.CharField( label="Effect subtype", - widget=selectable.AutoCompleteWidget(lookups.RelatedEffectSubtypeLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.OutcomeAutocomplete, field="effect_subtype" + ), help_text="ex: Melanoma", required=False, ) @@ -519,10 +543,24 @@ class OutcomeFilterForm(forms.Form): 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: - if field not in ("design", "diagnostic", "metric_units", "order_by", "paginate_by"): - self.fields[field].widget.update_query_parameters({"related": assessment.id}) + 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): @@ -588,7 +626,8 @@ def get_order_by(self): class OutcomeSelectorForm(CopyAsNewSelectorForm): label = "Outcome" - lookup_class = lookups.OutcomeByStudyPopulationLookup + parent_field = "study_population_id" + autocomplete_class = autocomplete.OutcomeAutocomplete class ComparisonSet(forms.ModelForm): @@ -647,12 +686,14 @@ def helper(self): class ComparisonSetByStudyPopulationSelectorForm(CopyAsNewSelectorForm): label = "Comparison set" - lookup_class = lookups.ComparisonSetByStudyPopulationLookup + parent_field = "study_population_id" + autocomplete_class = autocomplete.ComparisonSetAutocomplete class ComparisonSetByOutcomeSelectorForm(CopyAsNewSelectorForm): label = "Comparison set" - lookup_class = lookups.ComparisonSetByOutcomeLookup + parent_field = "outcome_id" + autocomplete_class = autocomplete.ComparisonSetAutocomplete class GroupForm(forms.ModelForm): @@ -765,32 +806,31 @@ class ResultForm(forms.ModelForm): HELP_TEXT_UPDATE = """Update results found for measured outcome.""" ADJUSTMENT_FIELDS = ["factors_applied", "factors_considered"] - factors_applied = selectable.AutoCompleteSelectMultipleField( + factors_applied = AutocompleteMultipleChoiceField( + autocomplete_class=autocomplete.AdjustmentFactorAutocomplete, help_text="All adjustment factors included in final statistical model", - lookup_class=lookups.AdjustmentFactorLookup, required=False, ) - factors_considered = selectable.AutoCompleteSelectMultipleField( + factors_considered = AutocompleteMultipleChoiceField( + autocomplete_class=autocomplete.AdjustmentFactorAutocomplete, label="Adjustment factors considered", help_text=models.OPTIONAL_NOTE, - lookup_class=lookups.AdjustmentFactorLookup, required=False, ) class Meta: model = models.Result exclude = ("outcome", "adjustment_factors") + widgets = { + "resulttags": AutocompleteSelectMultipleWidget(autocomplete_class=EffectTagAutocomplete) + } def __init__(self, *args, **kwargs): outcome = kwargs.pop("parent", None) super().__init__(*args, **kwargs) self.fields["comments"] = self.fields.pop("comments") # move to end - self.fields["resulttags"].widget = selectable.AutoCompleteSelectMultipleWidget( - lookup_class=EffectTagLookup - ) - if outcome: self.instance.outcome = outcome else: @@ -801,9 +841,7 @@ def __init__(self, *args, **kwargs): ) for fld in self.ADJUSTMENT_FIELDS: - self.fields[fld].widget.update_query_parameters( - {"related": self.instance.outcome.assessment_id} - ) + self.fields[fld].set_filters({"assessment_id": self.instance.outcome.assessment_id}) if self.instance.id: self.fields[fld].initial = getattr(self.instance, fld) @@ -898,7 +936,8 @@ def helper(self): class ResultSelectorForm(CopyAsNewSelectorForm): label = "Result" - lookup_class = lookups.ResultByOutcomeLookup + parent_field = "outcome_id" + autocomplete_class = autocomplete.ResultAutocomplete class ResultUpdateForm(ResultForm): diff --git a/hawc/apps/epi/lookups.py b/hawc/apps/epi/lookups.py deleted file mode 100644 index 6c1d223f12..0000000000 --- a/hawc/apps/epi/lookups.py +++ /dev/null @@ -1,180 +0,0 @@ -from selectable.registry import registry - -from ..common.lookups import DistinctStringLookup, RelatedDistinctStringLookup, RelatedLookup -from . import models - - -class StudyPopulationByAssessmentLookup(RelatedLookup): - model = models.StudyPopulation - search_fields = ("name__icontains",) - related_filter = "study__assessment_id" - - def get_query(self, request, term): - return super().get_query(request, term).distinct("name") - - -class StudyPopulationByStudyLookup(RelatedLookup): - model = models.StudyPopulation - search_fields = ("name__icontains",) - related_filter = "study_id" - - -class RelatedStudyPopulationAgeProfileLookup(RelatedDistinctStringLookup): - model = models.StudyPopulation - distinct_field = "age_profile" - related_filter = "study__assessment_id" - - -class RelatedStudyPopulationSourceLookup(RelatedDistinctStringLookup): - model = models.StudyPopulation - distinct_field = "source" - related_filter = "study__assessment_id" - - -class RelatedCountryNameLookup(RelatedDistinctStringLookup): - model = models.Country - distinct_field = "name" - related_filter = "studypopulation__study__assessment_id" - - -class RegionLookup(DistinctStringLookup): - model = models.StudyPopulation - distinct_field = "region" - - -class StateLookup(DistinctStringLookup): - model = models.StudyPopulation - distinct_field = "state" - - -class CriteriaLookup(RelatedLookup): - model = models.Criteria - search_fields = ("description__icontains",) - related_filter = "assessment_id" - - -class AdjustmentFactorLookup(RelatedLookup): - model = models.AdjustmentFactor - search_fields = ("description__icontains",) - related_filter = "assessment_id" - - -class ComparisonSetByStudyPopulationLookup(RelatedLookup): - model = models.ComparisonSet - search_fields = ("name__icontains",) - related_filter = "study_population_id" - - -class ComparisonSetByOutcomeLookup(ComparisonSetByStudyPopulationLookup): - related_filter = "outcome_id" - - -class ExposureByStudyPopulationLookup(RelatedLookup): - model = models.Exposure - search_fields = ("name__icontains",) - related_filter = "study_population_id" - - -class ExposureMeasuredLookup(DistinctStringLookup): - model = models.Exposure - distinct_field = "measured" - - -class ExposureMetricLookup(DistinctStringLookup): - model = models.Exposure - distinct_field = "metric" - - -class RelatedExposureMetricLookup(RelatedDistinctStringLookup): - model = models.Exposure - distinct_field = "metric" - related_filter = "study_population__study__assessment_id" - - -class AgeOfExposureLookup(DistinctStringLookup): - model = models.Exposure - distinct_field = "age_of_exposure" - - -class OutcomeLookup(RelatedLookup): - model = models.Outcome - search_fields = ("name__icontains",) - related_filter = "assessment_id" - - -class OutcomeByStudyPopulationLookup(RelatedLookup): - model = models.Outcome - search_fields = ("name__icontains",) - related_filter = "study_population_id" - - -class SystemLookup(DistinctStringLookup): - model = models.Outcome - distinct_field = "system" - - -class EffectLookup(DistinctStringLookup): - model = models.Outcome - distinct_field = "effect" - - -class EffectSubtypeLookup(DistinctStringLookup): - model = models.Outcome - distinct_field = "effect_subtype" - - -class AgeOfMeasurementLookup(DistinctStringLookup): - model = models.Outcome - distinct_field = "age_of_measurement" - - -class RelatedSystemLookup(RelatedDistinctStringLookup): - model = models.Outcome - distinct_field = "system" - related_filter = "assessment_id" - - -class RelatedEffectLookup(RelatedDistinctStringLookup): - model = models.Outcome - distinct_field = "effect" - related_filter = "assessment_id" - - -class RelatedEffectSubtypeLookup(RelatedDistinctStringLookup): - model = models.Outcome - distinct_field = "effect_subtype" - related_filter = "assessment_id" - - -class ResultByOutcomeLookup(RelatedLookup): - model = models.Result - search_fields = ("metric__metric__icontains", "comparison_set__name__icontains") - related_filter = "outcome_id" - - -registry.register(StudyPopulationByAssessmentLookup) -registry.register(StudyPopulationByStudyLookup) -registry.register(RelatedStudyPopulationAgeProfileLookup) -registry.register(RelatedStudyPopulationSourceLookup) -registry.register(RelatedCountryNameLookup) -registry.register(RegionLookup) -registry.register(StateLookup) -registry.register(CriteriaLookup) -registry.register(AdjustmentFactorLookup) -registry.register(ExposureByStudyPopulationLookup) -registry.register(ExposureMeasuredLookup) -registry.register(ExposureMetricLookup) -registry.register(RelatedExposureMetricLookup) -registry.register(AgeOfExposureLookup) -registry.register(ComparisonSetByStudyPopulationLookup) -registry.register(ComparisonSetByOutcomeLookup) -registry.register(OutcomeLookup) -registry.register(OutcomeByStudyPopulationLookup) -registry.register(SystemLookup) -registry.register(EffectLookup) -registry.register(EffectSubtypeLookup) -registry.register(RelatedSystemLookup) -registry.register(RelatedEffectLookup) -registry.register(RelatedEffectSubtypeLookup) -registry.register(AgeOfMeasurementLookup) -registry.register(ResultByOutcomeLookup) diff --git a/hawc/apps/epi/templates/epi/adjustmentfactor_form.html b/hawc/apps/epi/templates/epi/adjustmentfactor_form.html index 13196f6a95..48097de2a5 100644 --- a/hawc/apps/epi/templates/epi/adjustmentfactor_form.html +++ b/hawc/apps/epi/templates/epi/adjustmentfactor_form.html @@ -1,12 +1,7 @@ {% extends 'crumbless.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %} {% crispy form %} {% endblock content %} diff --git a/hawc/apps/epi/templates/epi/comparisonset_form.html b/hawc/apps/epi/templates/epi/comparisonset_form.html index e0208ffc7e..07757ee28a 100644 --- a/hawc/apps/epi/templates/epi/comparisonset_form.html +++ b/hawc/apps/epi/templates/epi/comparisonset_form.html @@ -1,12 +1,7 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %}
{% crispy form %} diff --git a/hawc/apps/epi/templates/epi/comparisonset_outcome_copy_selector.html b/hawc/apps/epi/templates/epi/comparisonset_outcome_copy_selector.html index f73914641e..8bb8adc280 100644 --- a/hawc/apps/epi/templates/epi/comparisonset_outcome_copy_selector.html +++ b/hawc/apps/epi/templates/epi/comparisonset_outcome_copy_selector.html @@ -1,11 +1,6 @@ {% extends 'assessment-rooted.html' %} {% load add_class %} -{% load selectable_tags %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} {% block content %} diff --git a/hawc/apps/epi/templates/epi/comparisonset_sp_copy_selector.html b/hawc/apps/epi/templates/epi/comparisonset_sp_copy_selector.html index f3dbc2130d..68841fda2b 100644 --- a/hawc/apps/epi/templates/epi/comparisonset_sp_copy_selector.html +++ b/hawc/apps/epi/templates/epi/comparisonset_sp_copy_selector.html @@ -1,11 +1,6 @@ {% extends 'assessment-rooted.html' %} {% load add_class %} -{% load selectable_tags %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} {% block content %} diff --git a/hawc/apps/epi/templates/epi/criteria_form.html b/hawc/apps/epi/templates/epi/criteria_form.html index 13196f6a95..48097de2a5 100644 --- a/hawc/apps/epi/templates/epi/criteria_form.html +++ b/hawc/apps/epi/templates/epi/criteria_form.html @@ -1,12 +1,7 @@ {% extends 'crumbless.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %} {% crispy form %} {% endblock content %} diff --git a/hawc/apps/epi/templates/epi/exposure_copy_selector.html b/hawc/apps/epi/templates/epi/exposure_copy_selector.html index cc814c88ba..bc6872298c 100644 --- a/hawc/apps/epi/templates/epi/exposure_copy_selector.html +++ b/hawc/apps/epi/templates/epi/exposure_copy_selector.html @@ -1,11 +1,6 @@ {% extends 'assessment-rooted.html' %} {% load add_class %} -{% load selectable_tags %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} {% block content %} diff --git a/hawc/apps/epi/templates/epi/exposure_form.html b/hawc/apps/epi/templates/epi/exposure_form.html index 8144f9d780..b12fda7b27 100644 --- a/hawc/apps/epi/templates/epi/exposure_form.html +++ b/hawc/apps/epi/templates/epi/exposure_form.html @@ -1,12 +1,7 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %}
{% crispy form %} diff --git a/hawc/apps/epi/templates/epi/group_form.html b/hawc/apps/epi/templates/epi/group_form.html index 7cacbd694d..ea48c18b76 100644 --- a/hawc/apps/epi/templates/epi/group_form.html +++ b/hawc/apps/epi/templates/epi/group_form.html @@ -1,12 +1,7 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %}
{% crispy form %} diff --git a/hawc/apps/epi/templates/epi/outcome_copy_selector.html b/hawc/apps/epi/templates/epi/outcome_copy_selector.html index de89918f63..817641251e 100644 --- a/hawc/apps/epi/templates/epi/outcome_copy_selector.html +++ b/hawc/apps/epi/templates/epi/outcome_copy_selector.html @@ -1,11 +1,6 @@ {% extends 'assessment-rooted.html' %} {% load add_class %} -{% load selectable_tags %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} {% block content %} {% include "hawc/_copy_as_new.html" with name="outcome" notes="Select an existing outcome as a template to create a new one." %} diff --git a/hawc/apps/epi/templates/epi/outcome_form.html b/hawc/apps/epi/templates/epi/outcome_form.html index 7251204a72..d436e80282 100644 --- a/hawc/apps/epi/templates/epi/outcome_form.html +++ b/hawc/apps/epi/templates/epi/outcome_form.html @@ -1,12 +1,7 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %} {% crispy form %} {% endblock %} diff --git a/hawc/apps/epi/templates/epi/outcome_list.html b/hawc/apps/epi/templates/epi/outcome_list.html index 538c175d1c..b4ef98a31f 100644 --- a/hawc/apps/epi/templates/epi/outcome_list.html +++ b/hawc/apps/epi/templates/epi/outcome_list.html @@ -1,7 +1,6 @@ {% extends 'assessment-rooted.html' %} {% load crispy_forms_tags %} -{% load selectable_tags %} {% block content %}

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

diff --git a/hawc/apps/epi/templates/epi/result_copy_selector.html b/hawc/apps/epi/templates/epi/result_copy_selector.html index 50288ee250..3f868f56a8 100644 --- a/hawc/apps/epi/templates/epi/result_copy_selector.html +++ b/hawc/apps/epi/templates/epi/result_copy_selector.html @@ -1,11 +1,6 @@ {% extends 'assessment-rooted.html' %} {% load add_class %} -{% load selectable_tags %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} {% block content %} diff --git a/hawc/apps/epi/templates/epi/result_form.html b/hawc/apps/epi/templates/epi/result_form.html index 57a7f4bfb6..5c8716c507 100644 --- a/hawc/apps/epi/templates/epi/result_form.html +++ b/hawc/apps/epi/templates/epi/result_form.html @@ -1,12 +1,7 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %}
{% crispy form %} diff --git a/hawc/apps/epi/templates/epi/studypopulation_copy_selector.html b/hawc/apps/epi/templates/epi/studypopulation_copy_selector.html index a4e8777142..0e46da47be 100644 --- a/hawc/apps/epi/templates/epi/studypopulation_copy_selector.html +++ b/hawc/apps/epi/templates/epi/studypopulation_copy_selector.html @@ -1,11 +1,6 @@ {% extends 'assessment-rooted.html' %} {% load add_class %} -{% load selectable_tags %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} {% block content %} diff --git a/hawc/apps/epi/templates/epi/studypopulation_form.html b/hawc/apps/epi/templates/epi/studypopulation_form.html index 3f875e6cf3..97cd980c5b 100644 --- a/hawc/apps/epi/templates/epi/studypopulation_form.html +++ b/hawc/apps/epi/templates/epi/studypopulation_form.html @@ -1,12 +1,7 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %} {% crispy form %} {% endblock %} diff --git a/hawc/apps/epimeta/autocomplete.py b/hawc/apps/epimeta/autocomplete.py new file mode 100644 index 0000000000..6314dbccc2 --- /dev/null +++ b/hawc/apps/epimeta/autocomplete.py @@ -0,0 +1,16 @@ +from ..common.autocomplete import BaseAutocomplete, register +from . import models + + +@register +class MetaProtocolAutocomplete(BaseAutocomplete): + model = models.MetaProtocol + search_fields = ["name"] + filter_fields = ["study__assessment_id"] + + +@register +class MetaResultAutocomplete(BaseAutocomplete): + model = models.MetaResult + search_fields = ["label"] + filter_fields = ["protocol_id", "protocol__study__assessment_id"] diff --git a/hawc/apps/epimeta/forms.py b/hawc/apps/epimeta/forms.py index 84f9792208..7bb280e401 100644 --- a/hawc/apps/epimeta/forms.py +++ b/hawc/apps/epimeta/forms.py @@ -5,11 +5,11 @@ from django.forms.models import modelformset_factory from django.urls import reverse -from ..common import selectable +from ..common.autocomplete import AutocompleteMultipleChoiceField, AutocompleteTextWidget from ..common.forms import BaseFormHelper, CopyAsNewSelectorForm, form_actions_apply_filters -from ..epi.lookups import AdjustmentFactorLookup, CriteriaLookup -from ..study.lookups import EpimetaStudyLookup -from . import lookups, models +from ..epi.autocomplete import AdjustmentFactorAutocomplete, CriteriaAutocomplete +from ..study.autocomplete import StudyAutocomplete +from . import autocomplete, models class MetaProtocolForm(forms.ModelForm): @@ -26,12 +26,12 @@ class MetaProtocolForm(forms.ModelForm): UPDATE_HELP_TEXT = "Update an existing meta-protocol" - inclusion_criteria = selectable.AutoCompleteSelectMultipleField( - lookup_class=CriteriaLookup, required=False + inclusion_criteria = AutocompleteMultipleChoiceField( + autocomplete_class=CriteriaAutocomplete, required=False ) - exclusion_criteria = selectable.AutoCompleteSelectMultipleField( - lookup_class=CriteriaLookup, required=False + exclusion_criteria = AutocompleteMultipleChoiceField( + autocomplete_class=CriteriaAutocomplete, required=False ) CRITERION_FIELDS = [ @@ -42,17 +42,21 @@ class MetaProtocolForm(forms.ModelForm): class Meta: model = models.MetaProtocol exclude = ("study",) + widgets = { + "lit_search_start_date": forms.DateInput(attrs={"type": "date"}), + "lit_search_end_date": forms.DateInput(attrs={"type": "date"}), + } def __init__(self, *args, **kwargs): parent = kwargs.pop("parent", None) super().__init__(*args, **kwargs) if parent: self.instance.study = parent - self.fields["inclusion_criteria"].widget.update_query_parameters( - {"related": self.instance.study.assessment_id} + self.fields["inclusion_criteria"].set_filters( + {"assessment_id": self.instance.study.assessment_id} ) - self.fields["exclusion_criteria"].widget.update_query_parameters( - {"related": self.instance.study.assessment_id} + self.fields["exclusion_criteria"].set_filters( + {"assessment_id": self.instance.study.assessment_id} ) @property @@ -101,35 +105,39 @@ class MetaResultForm(forms.ModelForm): UPDATE_HELP_TEXT = "Update an existing meta-result" - adjustment_factors = selectable.AutoCompleteSelectMultipleField( + adjustment_factors = AutocompleteMultipleChoiceField( help_text="All factors which were included in final model", - lookup_class=AdjustmentFactorLookup, + autocomplete_class=AdjustmentFactorAutocomplete, required=False, ) class Meta: model = models.MetaResult exclude = ("protocol",) + widgets = { + "health_outcome": AutocompleteTextWidget( + autocomplete_class=autocomplete.MetaResultAutocomplete, field="health_outcome" + ), + "exposure_name": AutocompleteTextWidget( + autocomplete_class=autocomplete.MetaResultAutocomplete, field="exposure_name" + ), + } def __init__(self, *args, **kwargs): parent = kwargs.pop("parent", None) assessment = kwargs.pop("assessment", None) super().__init__(*args, **kwargs) - self.fields["health_outcome"].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.MetaResultHealthOutcomeLookup, allow_new=True - ) - - self.fields["exposure_name"].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.MetaResultExposureNameLookup, allow_new=True - ) - if parent: self.instance.protocol = parent - self.fields["adjustment_factors"].widget.update_query_parameters({"related": assessment.id}) - self.fields["health_outcome"].widget.update_query_parameters({"related": assessment.id}) - self.fields["exposure_name"].widget.update_query_parameters({"related": assessment.id}) + self.fields["adjustment_factors"].set_filters({"assessment_id": assessment.id}) + self.fields["health_outcome"].widget.update_filters( + {"protocol__study__assessment_id": assessment.id} + ) + self.fields["exposure_name"].widget.update_filters( + {"protocol__study__assessment_id": assessment.id} + ) @property def helper(self): @@ -180,37 +188,45 @@ class MetaResultFilterForm(forms.Form): ("exposure", "exposure"), ) - studies = selectable.AutoCompleteSelectMultipleField( + studies = AutocompleteMultipleChoiceField( label="Study reference", - lookup_class=EpimetaStudyLookup, + autocomplete_class=StudyAutocomplete, help_text="ex: Smith et al. 2010", required=False, ) label = forms.CharField( label="Meta result label", - widget=selectable.AutoCompleteWidget(lookups.MetaResultByAssessmentLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.MetaResultAutocomplete, field="label" + ), help_text="ex: ALL, folic acid, any time", required=False, ) protocol = forms.CharField( label="Protocol", - widget=selectable.AutoCompleteWidget(lookups.MetaProtocolLookup), + 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=selectable.AutoCompleteWidget(lookups.MetaResultHealthOutcomeLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.MetaResultAutocomplete, field="health_outcome" + ), help_text="ex: Any adenoma", required=False, ) exposure_name = forms.CharField( label="Exposure name", - widget=selectable.AutoCompleteWidget(lookups.ExposureLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.MetaResultAutocomplete, field="exposure_name" + ), help_text="ex: Folate", required=False, ) @@ -226,9 +242,12 @@ class MetaResultFilterForm(forms.Form): 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: - if field not in ("order_by", "paginate_by"): - self.fields[field].widget.update_query_parameters({"related": assessment.id}) + 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): @@ -311,4 +330,5 @@ def __init__(self, **kwargs): class MetaResultSelectorForm(CopyAsNewSelectorForm): label = "Meta Result" - lookup_class = lookups.MetaResultByProtocolLookup + parent_field = "protocol_id" + autocomplete_class = autocomplete.MetaResultAutocomplete diff --git a/hawc/apps/epimeta/lookups.py b/hawc/apps/epimeta/lookups.py deleted file mode 100644 index b4d39bfbaf..0000000000 --- a/hawc/apps/epimeta/lookups.py +++ /dev/null @@ -1,51 +0,0 @@ -from selectable.registry import registry - -from ..common.lookups import RelatedDistinctStringLookup, RelatedLookup -from . import models - - -class MetaResultByProtocolLookup(RelatedLookup): - model = models.MetaResult - search_fields = ("label__icontains",) - related_filter = "protocol" - - -class MetaResultByAssessmentLookup(RelatedLookup): - model = models.MetaResult - search_fields = ("label__icontains",) - related_filter = "protocol__study__assessment_id" - - -class MetaResultHealthOutcomeLookup(RelatedDistinctStringLookup): - model = models.MetaResult - distinct_field = "health_outcome" - search_fields = ("health_outcome__icontains",) - related_filter = "protocol__study__assessment_id" - - -class MetaResultExposureNameLookup(RelatedDistinctStringLookup): - model = models.MetaResult - distinct_field = "exposure_name" - search_fields = ("exposure_name__icontains",) - related_filter = "protocol__study__assessment_id" - - -class MetaProtocolLookup(RelatedLookup): - model = models.MetaProtocol - search_fields = ("name__icontains",) - related_filter = "study__assessment_id" - - -class ExposureLookup(RelatedDistinctStringLookup): - model = models.MetaResult - distinct_field = "exposure_name" - search_fields = ("exposure_name__icontains",) - related_filter = "protocol__study__assessment_id" - - -registry.register(MetaResultByProtocolLookup) -registry.register(MetaResultByAssessmentLookup) -registry.register(MetaResultHealthOutcomeLookup) -registry.register(MetaResultExposureNameLookup) -registry.register(MetaProtocolLookup) -registry.register(ExposureLookup) diff --git a/hawc/apps/epimeta/templates/epimeta/metaprotocol_form.html b/hawc/apps/epimeta/templates/epimeta/metaprotocol_form.html index 9f2d840466..d436e80282 100644 --- a/hawc/apps/epimeta/templates/epimeta/metaprotocol_form.html +++ b/hawc/apps/epimeta/templates/epimeta/metaprotocol_form.html @@ -1,29 +1,7 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %} {% crispy form %} {% endblock %} - -{% block extrajs %} - -{% endblock extrajs %} diff --git a/hawc/apps/epimeta/templates/epimeta/metaresult_copy_selector.html b/hawc/apps/epimeta/templates/epimeta/metaresult_copy_selector.html index 6aaacf95fc..43bd0b1f87 100644 --- a/hawc/apps/epimeta/templates/epimeta/metaresult_copy_selector.html +++ b/hawc/apps/epimeta/templates/epimeta/metaresult_copy_selector.html @@ -1,11 +1,6 @@ {% extends 'assessment-rooted.html' %} {% load add_class %} -{% load selectable_tags %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} {% block content %} {% include "hawc/_copy_as_new.html" with name="meta-result" notes="Select an existing meta-result as a template to create a new one." %} diff --git a/hawc/apps/epimeta/templates/epimeta/metaresult_form.html b/hawc/apps/epimeta/templates/epimeta/metaresult_form.html index ce26ab73a9..b5399ad3fe 100644 --- a/hawc/apps/epimeta/templates/epimeta/metaresult_form.html +++ b/hawc/apps/epimeta/templates/epimeta/metaresult_form.html @@ -1,13 +1,8 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} {% load add_class %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %} {% crispy form %} {# Single-Result formset #} diff --git a/hawc/apps/epimeta/templates/epimeta/metaresult_list.html b/hawc/apps/epimeta/templates/epimeta/metaresult_list.html index a4eb9bf901..be378d7c1d 100644 --- a/hawc/apps/epimeta/templates/epimeta/metaresult_list.html +++ b/hawc/apps/epimeta/templates/epimeta/metaresult_list.html @@ -1,7 +1,6 @@ {% extends 'assessment-rooted.html' %} {% load crispy_forms_tags %} -{% load selectable_tags %} {% block content %}

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

diff --git a/hawc/apps/epiv2/autocomplete.py b/hawc/apps/epiv2/autocomplete.py new file mode 100644 index 0000000000..6ab9375e5c --- /dev/null +++ b/hawc/apps/epiv2/autocomplete.py @@ -0,0 +1,26 @@ +from ..common.autocomplete import BaseAutocomplete, register +from . import models + + +@register +class ChemicalAutocomplete(BaseAutocomplete): + model = models.Chemical + search_fields = ["name"] + + +@register +class ExposureLevelAutocomplete(BaseAutocomplete): + model = models.ExposureLevel + search_fields = ["name"] + + +@register +class OutcomeAutocomplete(BaseAutocomplete): + model = models.Outcome + search_fields = ["endpoint"] + + +@register +class DataExtractionAutocomplete(BaseAutocomplete): + model = models.DataExtraction + search_fields = [] diff --git a/hawc/apps/epiv2/forms.py b/hawc/apps/epiv2/forms.py index af61e8bde3..3bd2845362 100644 --- a/hawc/apps/epiv2/forms.py +++ b/hawc/apps/epiv2/forms.py @@ -3,11 +3,16 @@ from hawc.apps.common.forms import BaseFormHelper -from ..assessment.lookups import DssToxIdLookup -from ..common import selectable +from ..assessment.autocomplete import DSSToxAutocomplete +from ..common.autocomplete import ( + AutocompleteMultipleChoiceField, + AutocompleteSelectWidget, + AutocompleteTextWidget, +) from ..common.forms import ArrayCheckboxSelectMultiple, QuillField from ..common.widgets import SelectMultipleOtherWidget, SelectOtherWidget -from . import constants, lookups, models +from ..epi.autocomplete import CountryAutocomplete +from . import autocomplete, constants, models class DesignForm(forms.ModelForm): @@ -15,8 +20,8 @@ class DesignForm(forms.ModelForm): CREATE_HELP_TEXT = "" UPDATE_HELP_TEXT = "Update an existing study-population." - countries = selectable.AutoCompleteSelectMultipleField( - lookup_class=lookups.CountryNameLookup, required=False + countries = AutocompleteMultipleChoiceField( + autocomplete_class=CountryAutocomplete, required=False ) class Meta: @@ -64,10 +69,10 @@ class Meta: model = models.Chemical exclude = ("design",) widgets = { - "name": selectable.AutoCompleteWidget( - lookup_class=lookups.ChemicalNameLookup, allow_new=True + "name": AutocompleteTextWidget( + autocomplete_class=autocomplete.ChemicalAutocomplete, field="name" ), - "dsstox": selectable.AutoCompleteSelectWidget(lookup_class=DssToxIdLookup), + "dsstox": AutocompleteSelectWidget(autocomplete_class=DSSToxAutocomplete), } def __init__(self, *args, **kwargs): @@ -118,8 +123,8 @@ class Meta: model = models.ExposureLevel exclude = ("design",) widgets = { - "units": selectable.AutoCompleteWidget( - lookup_class=lookups.ExposureLevelUnitsLookup, allow_new=True + "units": AutocompleteTextWidget( + autocomplete_class=autocomplete.ExposureLevelAutocomplete, field="units" ), } @@ -192,14 +197,14 @@ class Meta: model = models.Outcome exclude = ("design",) widgets = { - "endpoint": selectable.AutoCompleteWidget( - lookup_class=lookups.EndpointLookup, allow_new=True + "endpoint": AutocompleteTextWidget( + autocomplete_class=autocomplete.OutcomeAutocomplete, field="endpoint" ), - "effect": selectable.AutoCompleteWidget( - lookup_class=lookups.EffectLookup, allow_new=True + "effect": AutocompleteTextWidget( + autocomplete_class=autocomplete.OutcomeAutocomplete, field="effect" ), - "effect_detail": selectable.AutoCompleteWidget( - lookup_class=lookups.EffectDetailLookup, allow_new=True + "effect_detail": AutocompleteTextWidget( + autocomplete_class=autocomplete.OutcomeAutocomplete, field="effect_detail" ), } @@ -226,11 +231,11 @@ class Meta: "exposure_transform": SelectOtherWidget(choices=constants.DataTransforms.choices), "outcome_transform": SelectOtherWidget(choices=constants.DataTransforms.choices), "effect_estimate_type": SelectOtherWidget(choices=constants.EffectEstimateType.choices), - "confidence": selectable.AutoCompleteWidget( - lookup_class=lookups.DataExtractionConfidenceLookup, allow_new=True + "confidence": AutocompleteTextWidget( + autocomplete_class=autocomplete.DataExtractionAutocomplete, field="confidence" ), - "units": selectable.AutoCompleteWidget( - lookup_class=lookups.DataExtractionUnitsLookup, allow_new=True + "units": AutocompleteTextWidget( + autocomplete_class=autocomplete.DataExtractionAutocomplete, field="units" ), } diff --git a/hawc/apps/epiv2/lookups.py b/hawc/apps/epiv2/lookups.py deleted file mode 100644 index 355dd93fd3..0000000000 --- a/hawc/apps/epiv2/lookups.py +++ /dev/null @@ -1,56 +0,0 @@ -from selectable.registry import registry - -from hawc.apps.epi.models import Country - -from ..common.lookups import DistinctStringLookup -from . import models - - -class CountryNameLookup(DistinctStringLookup): - model = Country - distinct_field = "name" - - -class ChemicalNameLookup(DistinctStringLookup): - model = models.Chemical - distinct_field = "name" - - -class ExposureLevelUnitsLookup(DistinctStringLookup): - model = models.ExposureLevel - distinct_field = "units" - - -class EffectLookup(DistinctStringLookup): - model = models.Outcome - distinct_field = "effect" - - -class EffectDetailLookup(DistinctStringLookup): - model = models.Outcome - distinct_field = "effect_detail" - - -class EndpointLookup(DistinctStringLookup): - model = models.Outcome - distinct_field = "endpoint" - - -class DataExtractionUnitsLookup(DistinctStringLookup): - model = models.DataExtraction - distinct_field = "units" - - -class DataExtractionConfidenceLookup(DistinctStringLookup): - model = models.DataExtraction - distinct_field = "confidence" - - -registry.register(CountryNameLookup) -registry.register(ChemicalNameLookup) -registry.register(EndpointLookup) -registry.register(EffectLookup) -registry.register(EffectDetailLookup) -registry.register(ExposureLevelUnitsLookup) -registry.register(DataExtractionUnitsLookup) -registry.register(DataExtractionConfidenceLookup) diff --git a/hawc/apps/epiv2/templates/epiv2/design_form.html b/hawc/apps/epiv2/templates/epiv2/design_form.html index 7a00cae709..3162743a04 100644 --- a/hawc/apps/epiv2/templates/epiv2/design_form.html +++ b/hawc/apps/epiv2/templates/epiv2/design_form.html @@ -1,12 +1,7 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %}
{% include "common/scientific_pulldown.html" %} diff --git a/hawc/apps/epiv2/templates/epiv2/design_update.html b/hawc/apps/epiv2/templates/epiv2/design_update.html index 6e27e86dc8..4d9a4cd838 100644 --- a/hawc/apps/epiv2/templates/epiv2/design_update.html +++ b/hawc/apps/epiv2/templates/epiv2/design_update.html @@ -144,6 +144,7 @@

Update {{object.summary}}

{% block extrajs %} +{% include "common/htmx_autocomplete.html" %} - - {% endblock content %} diff --git a/hawc/apps/invitro/autocomplete.py b/hawc/apps/invitro/autocomplete.py new file mode 100644 index 0000000000..a5f9295e28 --- /dev/null +++ b/hawc/apps/invitro/autocomplete.py @@ -0,0 +1,30 @@ +from ..common.autocomplete import BaseAutocomplete, register +from . import models + + +@register +class IVChemicalAutocomplete(BaseAutocomplete): + model = models.IVChemical + search_fields = ["name"] + filter_fields = ["study__assessment_id"] + + +@register +class IVCellTypeAutocomplete(BaseAutocomplete): + model = models.IVCellType + search_fields = ["cell_type"] + filter_fields = ["study__assessment_id"] + + +@register +class IVExperimentAutocomplete(BaseAutocomplete): + model = models.IVExperiment + search_fields = ["name"] + filter_fields = ["study__assessment_id"] + + +@register +class IVEndpointAutocomplete(BaseAutocomplete): + model = models.IVEndpoint + search_fields = ["short_description"] + filter_fields = ["experiment__study__assessment_id"] diff --git a/hawc/apps/invitro/forms.py b/hawc/apps/invitro/forms.py index aad2f2e642..bc86912ddd 100644 --- a/hawc/apps/invitro/forms.py +++ b/hawc/apps/invitro/forms.py @@ -6,12 +6,17 @@ from django.forms.widgets import Select from django.urls import reverse -from ..assessment.lookups import DssToxIdLookup, EffectTagLookup +from ..assessment.autocomplete import DSSToxAutocomplete, EffectTagAutocomplete from ..assessment.models import DoseUnits -from ..common import selectable +from ..common.autocomplete import ( + AutocompleteChoiceField, + AutocompleteMultipleChoiceField, + AutocompleteSelectMultipleWidget, + AutocompleteTextWidget, +) from ..common.forms import BaseFormHelper, form_actions_apply_filters -from ..study.lookups import InvitroStudyLookup -from . import lookups, models +from ..study.autocomplete import StudyAutocomplete +from . import autocomplete, models class IVChemicalForm(forms.ModelForm): @@ -20,7 +25,9 @@ class IVChemicalForm(forms.ModelForm): source = forms.CharField( label="Source of chemical", - widget=selectable.AutoCompleteWidget(lookups.IVChemicalSourceLookup, allow_new=True), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVChemicalAutocomplete, field="source" + ), ) class Meta: @@ -33,12 +40,9 @@ def __init__(self, *args, **kwargs): if study: self.instance.study = study - self.fields["source"].widget.update_query_parameters( - {"related": self.instance.study.assessment.id} - ) - - self.fields["dtxsid"].widget = selectable.AutoCompleteSelectWidget( - lookup_class=DssToxIdLookup + self.fields["dtxsid"] = AutocompleteChoiceField(autocomplete_class=DSSToxAutocomplete) + self.fields["source"].widget.update_filters( + {"study__assessment_id": self.instance.study.assessment_id} ) @property @@ -79,23 +83,33 @@ class IVCellTypeForm(forms.ModelForm): species = forms.CharField( label="Species", - widget=selectable.AutoCompleteWidget(lookups.IVCellTypeSpeciesLookup, allow_new=True), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVCellTypeAutocomplete, field="species" + ), ) strain = forms.CharField( label="Strain", - widget=selectable.AutoCompleteWidget(lookups.IVCellTypeStrainLookup, allow_new=True), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVCellTypeAutocomplete, field="strain" + ), ) cell_type = forms.CharField( label="Cell type", - widget=selectable.AutoCompleteWidget(lookups.IVCellTypeCellTypeLookup, allow_new=True), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVCellTypeAutocomplete, field="cell_type" + ), ) tissue = forms.CharField( label="Tissue", - widget=selectable.AutoCompleteWidget(lookups.IVCellTypeTissueLookup, allow_new=True), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVCellTypeAutocomplete, field="tissue" + ), ) source = forms.CharField( label="Source of cell cultures", - widget=selectable.AutoCompleteWidget(lookups.IVCellTypeSourceLookup, allow_new=True), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVCellTypeAutocomplete, field="source" + ), ) class Meta: @@ -109,8 +123,8 @@ def __init__(self, *args, **kwargs): self.instance.study = study for field in ("species", "strain", "cell_type", "tissue", "source"): - self.fields[field].widget.update_query_parameters( - {"related": self.instance.study.assessment.id} + self.fields[field].widget.update_filters( + {"study__assessment_id": self.instance.study.assessment.id} ) @property @@ -148,21 +162,23 @@ class IVExperimentForm(forms.ModelForm): HELP_TEXT_UPDATE = "Update an existing experiment." transfection = forms.CharField( - widget=selectable.AutoCompleteWidget(lookups.IVExperimentTransfectionLookup, allow_new=True) + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVExperimentAutocomplete, field="transfection" + ) ) positive_control = forms.CharField( - widget=selectable.AutoCompleteWidget( - lookups.IVExperimentPositiveControlLookup, allow_new=True + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVExperimentAutocomplete, field="positive_control" ) ) negative_control = forms.CharField( - widget=selectable.AutoCompleteWidget( - lookups.IVExperimentNegativeControlLookup, allow_new=True + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVExperimentAutocomplete, field="negative_control" ) ) vehicle_control = forms.CharField( - widget=selectable.AutoCompleteWidget( - lookups.IVExperimentVehicleControlLookup, allow_new=True + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVExperimentAutocomplete, field="vehicle_control" ) ) @@ -185,8 +201,8 @@ def __init__(self, *args, **kwargs): "negative_control", "vehicle_control", ): - self.fields[field].widget.update_query_parameters( - {"related": self.instance.study.assessment.id} + self.fields[field].widget.update_filters( + {"study__assessment_id": self.instance.study.assessment.id} ) @property @@ -250,11 +266,15 @@ class IVEndpointForm(forms.ModelForm): ) assay_type = forms.CharField( label="Assay Type", - widget=selectable.AutoCompleteWidget(lookups.IVEndpointAssayTypeLookup, allow_new=True), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVEndpointAutocomplete, field="assay_type" + ), ) response_units = forms.CharField( label="Response Units", - widget=selectable.AutoCompleteWidget(lookups.IVEndpointResponseUnitsLookup, allow_new=True), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVEndpointAutocomplete, field="response_units" + ), ) class Meta: @@ -266,6 +286,7 @@ class Meta: widgets = { "NOEL": OELWidget(), "LOEL": OELWidget(), + "effects": AutocompleteSelectMultipleWidget(autocomplete_class=EffectTagAutocomplete), } def __init__(self, *args, **kwargs): @@ -280,13 +301,10 @@ def __init__(self, *args, **kwargs): self.fields["NOEL"].widget.set_default_choices(self.instance) self.fields["LOEL"].widget.set_default_choices(self.instance) - self.fields["effect"].widget = selectable.AutoCompleteWidget( - lookups.IVEndpointEffectLookup, allow_new=True + self.fields["effect"].widget = AutocompleteTextWidget( + autocomplete_class=autocomplete.IVEndpointAutocomplete, field="effect" ) - self.fields["effects"].widget = selectable.AutoCompleteSelectMultipleWidget( - lookup_class=EffectTagLookup - ) self.fields["effects"].help_text = "Tags used to help categorize effect description." self.fields["chemical"].queryset = self.fields["chemical"].queryset.filter( @@ -298,8 +316,8 @@ def __init__(self, *args, **kwargs): ) for field in ("assay_type", "response_units", "effect"): - self.fields[field].widget.update_query_parameters( - {"related": self.instance.assessment_id} + self.fields[field].widget.update_filters( + {"experiment__study__assessment_id": self.instance.assessment_id} ) def clean_additional_fields(self): @@ -363,58 +381,72 @@ class IVEndpointFilterForm(forms.Form): ("response_units", "response units"), ) - studies = selectable.AutoCompleteSelectMultipleField( + studies = AutocompleteMultipleChoiceField( label="Study reference", - lookup_class=InvitroStudyLookup, + autocomplete_class=StudyAutocomplete, help_text="ex: Smith et al. 2010", required=False, ) name = forms.CharField( label="Endpoint name", - widget=selectable.AutoCompleteWidget(lookups.IVEndpointByAssessmentTextLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVEndpointAutocomplete, field="name" + ), help_text="ex: B cells", required=False, ) chemical = forms.CharField( label="Chemical name", - widget=selectable.AutoCompleteWidget(lookups.RelatedIVChemicalNameLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVChemicalAutocomplete, field="name" + ), help_text="ex: PFOA", required=False, ) cas = forms.CharField( label="CAS", - widget=selectable.AutoCompleteWidget(lookups.RelatedIVChemicalCASLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVChemicalAutocomplete, field="cas" + ), help_text="ex: 107-02-8", required=False, ) cell_type = forms.CharField( label="Cell type", - widget=selectable.AutoCompleteWidget(lookups.RelatedIVCellTypeNameLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVCellTypeAutocomplete, field="cell_type" + ), help_text="ex: HeLa", required=False, ) tissue = forms.CharField( label="Tissue", - widget=selectable.AutoCompleteWidget(lookups.RelatedIVCellTypeTissueLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVCellTypeAutocomplete, field="tissue" + ), help_text="ex: adipocytes", required=False, ) effect = forms.CharField( label="Effect", - widget=selectable.AutoCompleteWidget(lookups.RelatedIVEndpointEffectLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVEndpointAutocomplete, field="effect" + ), help_text="ex: gene expression", required=False, ) response_units = forms.CharField( label="Response units", - widget=selectable.AutoCompleteWidget(lookups.RelatedIVEndpointResponseUnitsLookup), + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVEndpointAutocomplete, field="response_units" + ), help_text="ex: counts", required=False, ) @@ -433,9 +465,18 @@ 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) - for field in self.fields: - if field not in ("dose_units", "order_by", "paginate_by"): - self.fields[field].widget.update_query_parameters({"related": 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): diff --git a/hawc/apps/invitro/lookups.py b/hawc/apps/invitro/lookups.py deleted file mode 100644 index 1e2b9e2ec9..0000000000 --- a/hawc/apps/invitro/lookups.py +++ /dev/null @@ -1,153 +0,0 @@ -from selectable.registry import registry - -from ..common.lookups import DistinctStringLookup, RelatedDistinctStringLookup, RelatedLookup -from . import models - - -# Chemical -class RelatedIVChemicalNameLookup(RelatedDistinctStringLookup): - model = models.IVChemical - distinct_field = "name" - related_filter = "study__assessment_id" - - -class RelatedIVChemicalCASLookup(RelatedDistinctStringLookup): - model = models.IVChemical - distinct_field = "cas" - related_filter = "study__assessment_id" - - -class IVChemicalSourceLookup(DistinctStringLookup): - model = models.IVChemical - distinct_field = "source" - - -class IVChemicalPurityLookup(DistinctStringLookup): - model = models.IVChemical - distinct_field = "purity" - - -# CellType -class RelatedIVCellTypeNameLookup(RelatedDistinctStringLookup): - model = models.IVCellType - distinct_field = "cell_type" - related_filter = "study__assessment_id" - - -class IVCellTypeSpeciesLookup(DistinctStringLookup): - model = models.IVCellType - distinct_field = "species" - - -class IVCellTypeStrainLookup(DistinctStringLookup): - model = models.IVCellType - distinct_field = "strain" - - -class IVCellTypeCellTypeLookup(DistinctStringLookup): - model = models.IVCellType - distinct_field = "cell_type" - - -class RelatedIVCellTypeTissueLookup(RelatedDistinctStringLookup): - model = models.IVCellType - distinct_field = "tissue" - related_filter = "study__assessment_id" - - -class IVCellTypeTissueLookup(DistinctStringLookup): - model = models.IVCellType - distinct_field = "tissue" - - -class IVCellTypeSourceLookup(DistinctStringLookup): - model = models.IVCellType - distinct_field = "source" - - -# Experiment -class IVExperimentTransfectionLookup(DistinctStringLookup): - model = models.IVExperiment - distinct_field = "transfection" - - -class IVExperimentPositiveControlLookup(DistinctStringLookup): - model = models.IVExperiment - distinct_field = "positive_control" - - -class IVExperimentNegativeControlLookup(DistinctStringLookup): - model = models.IVExperiment - distinct_field = "negative_control" - - -class IVExperimentVehicleControlLookup(DistinctStringLookup): - model = models.IVExperiment - distinct_field = "vehicle_control" - - -# Endpoint -class RelatedIVEndpointEffectLookup(RelatedDistinctStringLookup): - model = models.IVEndpoint - distinct_field = "effect" - related_filter = "assessment_id" - - -class IVEndpointEffectLookup(DistinctStringLookup): - model = models.IVEndpoint - distinct_field = "effect" - - -class IVEndpointAssayTypeLookup(DistinctStringLookup): - model = models.IVEndpoint - distinct_field = "assay_type" - - -class IVEndpointResponseUnitsLookup(DistinctStringLookup): - model = models.IVEndpoint - distinct_field = "response_units" - - -class RelatedIVEndpointResponseUnitsLookup(RelatedDistinctStringLookup): - model = models.IVEndpoint - distinct_field = "response_units" - related_filter = "assessment_id" - - -class IVEndpointByAssessmentTextLookup(RelatedLookup): - model = models.IVEndpoint - search_fields = ("name__icontains",) - related_filter = "assessment_id" - - def get_query(self, request, term): - return super().get_query(request, term).distinct("name") - - -# Chemical -registry.register(RelatedIVChemicalNameLookup) -registry.register(RelatedIVChemicalCASLookup) -registry.register(IVChemicalSourceLookup) -registry.register(IVChemicalPurityLookup) - -# CellType -registry.register(RelatedIVCellTypeNameLookup) -registry.register(IVCellTypeSpeciesLookup) -registry.register(IVCellTypeStrainLookup) -registry.register(IVCellTypeCellTypeLookup) -registry.register(RelatedIVCellTypeTissueLookup) -registry.register(IVCellTypeTissueLookup) -registry.register(IVCellTypeSourceLookup) - -# Experiment -registry.register(IVExperimentTransfectionLookup) -registry.register(IVExperimentPositiveControlLookup) -registry.register(IVExperimentNegativeControlLookup) -registry.register(IVExperimentVehicleControlLookup) - -# Endpoint -registry.register(RelatedIVEndpointEffectLookup) -registry.register(IVEndpointEffectLookup) -registry.register(IVEndpointAssayTypeLookup) -registry.register(IVEndpointResponseUnitsLookup) -registry.register(RelatedIVEndpointResponseUnitsLookup) -registry.register(IVEndpointByAssessmentTextLookup) diff --git a/hawc/apps/invitro/templates/invitro/ivcelltype_form.html b/hawc/apps/invitro/templates/invitro/ivcelltype_form.html index 7251204a72..d436e80282 100644 --- a/hawc/apps/invitro/templates/invitro/ivcelltype_form.html +++ b/hawc/apps/invitro/templates/invitro/ivcelltype_form.html @@ -1,12 +1,7 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %} {% crispy form %} {% endblock %} diff --git a/hawc/apps/invitro/templates/invitro/ivchemical_form.html b/hawc/apps/invitro/templates/invitro/ivchemical_form.html index b208922f04..1cf49a18af 100644 --- a/hawc/apps/invitro/templates/invitro/ivchemical_form.html +++ b/hawc/apps/invitro/templates/invitro/ivchemical_form.html @@ -1,12 +1,7 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %} {% crispy form %} {% endblock %} @@ -18,12 +13,14 @@ // handle all logic related to dtxid + cas var handleChemicalPropertiesInformation = function () { // if `dtxsid` is selected, change casrn/chemical_name - $('input[name=dtxsid_0]').bind('djselectableselect', function (event, item) { - if (item.item.casrn) { - $('#id_cas').val(item.item.casrn); + $('select[name=dtxsid]').on('select2:select', function (event) { + let casrn = event.params.data.casrn, + chemical_name = event.params.data.chemical_name; + if (casrn) { + $('#id_cas').val(casrn); } - if (item.item.chemical_name) { - $('#id_name').val(item.item.chemical_name); + if (chemical_name) { + $('#id_name').val(chemical_name); } }); diff --git a/hawc/apps/invitro/templates/invitro/ivendpoint_form.html b/hawc/apps/invitro/templates/invitro/ivendpoint_form.html index a4c12ddf3e..cd07c3148f 100644 --- a/hawc/apps/invitro/templates/invitro/ivendpoint_form.html +++ b/hawc/apps/invitro/templates/invitro/ivendpoint_form.html @@ -1,12 +1,7 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %}
{% crispy form %} diff --git a/hawc/apps/invitro/templates/invitro/ivendpoint_list.html b/hawc/apps/invitro/templates/invitro/ivendpoint_list.html index 81a1cbef21..ba0b1bd9a6 100644 --- a/hawc/apps/invitro/templates/invitro/ivendpoint_list.html +++ b/hawc/apps/invitro/templates/invitro/ivendpoint_list.html @@ -1,7 +1,6 @@ {% extends 'assessment-rooted.html' %} {% load crispy_forms_tags %} -{% load selectable_tags %} {% block content %}

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

diff --git a/hawc/apps/invitro/templates/invitro/ivexperiment_form.html b/hawc/apps/invitro/templates/invitro/ivexperiment_form.html index 2dd31000bf..f880122a93 100644 --- a/hawc/apps/invitro/templates/invitro/ivexperiment_form.html +++ b/hawc/apps/invitro/templates/invitro/ivexperiment_form.html @@ -1,12 +1,7 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %} {% crispy form %} {% endblock %} diff --git a/hawc/apps/lit/templates/lit/reference_form.html b/hawc/apps/lit/templates/lit/reference_form.html index 4b53fd8298..f735f158e0 100644 --- a/hawc/apps/lit/templates/lit/reference_form.html +++ b/hawc/apps/lit/templates/lit/reference_form.html @@ -1,6 +1,5 @@ {% extends 'assessment-rooted.html' %} -{% load selectable_tags %} {% load crispy_forms_tags %} {% block content %} diff --git a/hawc/apps/lit/templates/lit/search_copy_selector.html b/hawc/apps/lit/templates/lit/search_copy_selector.html index 8ab6149f18..8f4677d34e 100644 --- a/hawc/apps/lit/templates/lit/search_copy_selector.html +++ b/hawc/apps/lit/templates/lit/search_copy_selector.html @@ -1,13 +1,8 @@ {% extends 'assessment-rooted.html' %} {% load add_class %} -{% load selectable_tags %} {% load crispy_forms_tags %} -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - {% block content %} {% crispy form %} {% endblock content %} diff --git a/hawc/apps/mgmt/views.py b/hawc/apps/mgmt/views.py index 798dead0d3..b986b2389b 100644 --- a/hawc/apps/mgmt/views.py +++ b/hawc/apps/mgmt/views.py @@ -82,11 +82,7 @@ def get_app_config(self, context) -> WebappConfig: "url": task_url, "list": self.model.get_qs_json(context["object_list"], json_encode=False), }, - "autocomplete": { - "url": reverse( - "selectable-lookup", args=("myuser-assessmentteammemberorhigherlookup",) - ) - }, + "autocomplete": {"url": reverse("autocomplete", args=("myuser-userautocomplete",))}, }, ) @@ -174,8 +170,6 @@ def get_app_config(self, context) -> WebappConfig: tasksListUrl=reverse("mgmt:api:task-list") + f"?assessment_id={a_id}", taskUpdateBaseUrl=reverse("mgmt:api:task-list"), studyListUrl=reverse("study:api:study-list") + f"?assessment_id={a_id}", - userAutocompleteUrl=reverse( - "selectable-lookup", args=("myuser-assessmentteammemberorhigherlookup",) - ), + userAutocompleteUrl=reverse("autocomplete", args=("myuser-userautocomplete",)), ), ) diff --git a/hawc/apps/myuser/autocomplete.py b/hawc/apps/myuser/autocomplete.py new file mode 100644 index 0000000000..5fd0c2c91e --- /dev/null +++ b/hawc/apps/myuser/autocomplete.py @@ -0,0 +1,27 @@ +from django.db.models import Q + +from ..common.autocomplete import BaseAutocomplete, register +from . import models + + +@register +class UserAutocomplete(BaseAutocomplete): + model = models.HAWCUser + search_fields = ["first_name", "last_name", "email"] + + @classmethod + def get_base_queryset(cls, filters: dict = None): + qs = super().get_base_queryset(filters) + # if assessment id is provided, return team member or higher + if (assessment_id := filters.get("assessment_id")) is not None: + qs = qs.filter( + Q(assessment_pms__id=assessment_id) | Q(assessment_teams__id=assessment_id) + ).distinct() + return qs + + def get_queryset(self): + # only get active user choices + return super().get_queryset().filter(is_active=True) + + def get_result_label(self, result): + return str(result) if result.is_active else "" diff --git a/hawc/apps/myuser/forms.py b/hawc/apps/myuser/forms.py index 0c86380f15..b788ab7eda 100644 --- a/hawc/apps/myuser/forms.py +++ b/hawc/apps/myuser/forms.py @@ -12,10 +12,10 @@ from django.urls import reverse from ...constants import AuthProvider -from ..assessment import lookups +from ..assessment.autocomplete import AssessmentAutocomplete +from ..common.autocomplete import AutocompleteMultipleChoiceField from ..common.forms import BaseFormHelper from ..common.helper import url_query -from ..common.selectable import AutoCompleteSelectMultipleField from . import models _PASSWORD_HELP = ( @@ -347,14 +347,14 @@ def clean_email(self): class AdminUserForm(PasswordForm): - project_manager = AutoCompleteSelectMultipleField( - lookup_class=lookups.AssessmentLookup, label="Project manager", required=False + project_manager = AutocompleteMultipleChoiceField( + autocomplete_class=AssessmentAutocomplete, label="Project manager", required=False ) - team_member = AutoCompleteSelectMultipleField( - lookup_class=lookups.AssessmentLookup, label="Team member", required=False + team_member = AutocompleteMultipleChoiceField( + autocomplete_class=AssessmentAutocomplete, label="Team member", required=False ) - reviewer = AutoCompleteSelectMultipleField( - lookup_class=lookups.AssessmentLookup, label="Reviewer", required=False + reviewer = AutocompleteMultipleChoiceField( + autocomplete_class=AssessmentAutocomplete, label="Reviewer", required=False ) class Meta: @@ -383,6 +383,9 @@ def __init__(self, *args, **kwargs): ): self.fields[field].disabled = True + for field in ["project_manager", "team_member", "reviewer"]: + self.fields[field].widget.attrs["data-theme"] = "default" + if self.instance.id: self.fields["password1"].required = False self.fields["password2"].required = False diff --git a/hawc/apps/myuser/lookups.py b/hawc/apps/myuser/lookups.py deleted file mode 100644 index 90cb03d5b7..0000000000 --- a/hawc/apps/myuser/lookups.py +++ /dev/null @@ -1,40 +0,0 @@ -import operator -from functools import reduce - -from django.db.models import Q -from selectable.base import ModelLookup -from selectable.registry import registry - -from . import models - - -class HAWCUserLookup(ModelLookup): - model = models.HAWCUser - search_fields = ( - "first_name__icontains", - "last_name__icontains", - "email__icontains", - ) - filters = { - "is_active": True, - } - - def get_item_value(self, obj): - return str(obj) if obj is not None else "<inactive user>" - - -class AssessmentTeamMemberOrHigherLookup(HAWCUserLookup): - def get_query(self, request, term): - try: - id_ = int(request.GET.get("related", -1)) - except ValueError: - id_ = -1 - search_fields = [Q(**{field: term}) for field in self.search_fields] - return self.model.objects.filter( - (Q(assessment_pms__id=id_) | Q(assessment_teams__id=id_)) - & reduce(operator.or_, search_fields) - ).distinct() - - -registry.register(HAWCUserLookup) -registry.register(AssessmentTeamMemberOrHigherLookup) diff --git a/hawc/apps/study/autocomplete.py b/hawc/apps/study/autocomplete.py new file mode 100644 index 0000000000..d98aae21c2 --- /dev/null +++ b/hawc/apps/study/autocomplete.py @@ -0,0 +1,9 @@ +from ..common.autocomplete import BaseAutocomplete, register +from . import models + + +@register +class StudyAutocomplete(BaseAutocomplete): + model = models.Study + search_fields = ["short_citation"] + filter_fields = ["assessment_id", "bioassay", "epi", "epi_meta", "in_vitro"] diff --git a/hawc/apps/study/lookups.py b/hawc/apps/study/lookups.py deleted file mode 100644 index 4d3af7ed1f..0000000000 --- a/hawc/apps/study/lookups.py +++ /dev/null @@ -1,41 +0,0 @@ -from selectable.registry import registry - -from ..common.lookups import RelatedLookup -from . import models - - -class StudyLookup(RelatedLookup): - model = models.Study - search_fields = ("short_citation__icontains",) - related_filter = "assessment_id" - - -class AnimalStudyLookup(StudyLookup): - filters = { - "bioassay": True, - } - - -class EpiStudyLookup(StudyLookup): - filters = { - "epi": True, - } - - -class EpimetaStudyLookup(StudyLookup): - filters = { - "epi_meta": True, - } - - -class InvitroStudyLookup(StudyLookup): - filters = { - "in_vitro": True, - } - - -registry.register(StudyLookup) -registry.register(AnimalStudyLookup) -registry.register(EpiStudyLookup) -registry.register(EpimetaStudyLookup) -registry.register(InvitroStudyLookup) diff --git a/hawc/apps/summary/autocomplete.py b/hawc/apps/summary/autocomplete.py new file mode 100644 index 0000000000..3db4d0a524 --- /dev/null +++ b/hawc/apps/summary/autocomplete.py @@ -0,0 +1,16 @@ +from ..common.autocomplete import BaseAutocomplete, register +from . import models + + +@register +class DataPivotAutocomplete(BaseAutocomplete): + model = models.DataPivot + search_fields = ["title"] + filter_fields = ["assessment_id"] + + +@register +class VisualAutocomplete(BaseAutocomplete): + model = models.Visual + search_fields = ["title"] + filter_fields = ["assessment_id"] diff --git a/hawc/apps/summary/forms.py b/hawc/apps/summary/forms.py index 6dde6d8a6d..2d933b4a43 100644 --- a/hawc/apps/summary/forms.py +++ b/hawc/apps/summary/forms.py @@ -9,17 +9,17 @@ from openpyxl import load_workbook from openpyxl.utils.exceptions import InvalidFileException -from ..animal.lookups import EndpointByAssessmentLookup +from ..animal.autocomplete import EndpointAutocomplete from ..animal.models import Endpoint from ..assessment.models import DoseUnits, EffectTag -from ..common import selectable +from ..common.autocomplete import AutocompleteChoiceField from ..common.forms import ASSESSMENT_UNIQUE_MESSAGE, BaseFormHelper, form_actions_apply_filters from ..epi.models import Outcome from ..invitro.models import IVChemical, IVEndpointCategory from ..lit.models import ReferenceFilterTag -from ..study.lookups import StudyLookup +from ..study.autocomplete import StudyAutocomplete from ..study.models import Study -from . import constants, lookups, models +from . import autocomplete, constants, models def clean_slug(form): @@ -1202,27 +1202,29 @@ class SmartTagForm(forms.Form): ("data_pivot", "Data Pivot"), ) resource = forms.ChoiceField(choices=RESOURCE_CHOICES) - study = selectable.AutoCompleteSelectField( - lookup_class=StudyLookup, + study = AutocompleteChoiceField( + autocomplete_class=StudyAutocomplete, help_text="Type a few characters of the study name, then click to select.", ) - endpoint = selectable.AutoCompleteSelectField( - lookup_class=EndpointByAssessmentLookup, + endpoint = AutocompleteChoiceField( + autocomplete_class=EndpointAutocomplete, help_text="Type a few characters of the endpoint name, then click to select.", ) - visual = selectable.AutoCompleteSelectField( - lookup_class=lookups.VisualLookup, + visual = AutocompleteChoiceField( + autocomplete_class=autocomplete.VisualAutocomplete, help_text="Type a few characters of the visual name, then click to select.", ) - data_pivot = selectable.AutoCompleteSelectField( - lookup_class=lookups.DataPivotLookup, + data_pivot = AutocompleteChoiceField( + autocomplete_class=autocomplete.DataPivotAutocomplete, help_text="Type a few characters of the data-pivot name, then click to select.", ) def __init__(self, *args, **kwargs): assessment_id = kwargs.pop("assessment_id", -1) super().__init__(*args, **kwargs) - for fld in list(self.fields.keys()): - widget = self.fields[fld].widget - if hasattr(widget, "update_query_parameters"): - widget.update_query_parameters({"related": assessment_id}) + self.fields["study"].set_filters({"assessment_id": assessment_id}) + self.fields["endpoint"].set_filters( + {"animal_group__experiment__study__assessment_id": assessment_id} + ) + self.fields["visual"].set_filters({"assessment_id": assessment_id}) + self.fields["data_pivot"].set_filters({"assessment_id": assessment_id}) diff --git a/hawc/apps/summary/lookups.py b/hawc/apps/summary/lookups.py deleted file mode 100644 index 20b2f65810..0000000000 --- a/hawc/apps/summary/lookups.py +++ /dev/null @@ -1,20 +0,0 @@ -from selectable.registry import registry - -from ..common.lookups import RelatedLookup -from . import models - - -class DataPivotLookup(RelatedLookup): - model = models.DataPivot - search_fields = ("title__icontains",) - related_filter = "assessment_id" - - -class VisualLookup(RelatedLookup): - model = models.Visual - search_fields = ("title__icontains",) - related_filter = "assessment_id" - - -registry.register(DataPivotLookup) -registry.register(VisualLookup) diff --git a/hawc/apps/summary/templates/summary/datapivot_form.html b/hawc/apps/summary/templates/summary/datapivot_form.html index 15e40bd303..a0934824c1 100644 --- a/hawc/apps/summary/templates/summary/datapivot_form.html +++ b/hawc/apps/summary/templates/summary/datapivot_form.html @@ -2,7 +2,6 @@ {% load crispy_forms_tags %} {% load add_class %} -{% load selectable_tags %} {% block content %} {% include "assessment/preferred_dose_units_widget.html" %} diff --git a/hawc/apps/summary/templates/summary/summarytext_form.html b/hawc/apps/summary/templates/summary/summarytext_form.html index 3adc5513b1..9942b553ef 100644 --- a/hawc/apps/summary/templates/summary/summarytext_form.html +++ b/hawc/apps/summary/templates/summary/summarytext_form.html @@ -1,7 +1,6 @@ {% extends 'assessment-rooted.html' %} {% load add_class %} -{% load selectable_tags %} {% load crispy_forms_tags %} {% block content %} diff --git a/hawc/apps/summary/templates/summary/visual_form.html b/hawc/apps/summary/templates/summary/visual_form.html index fff3e1309e..cd89f8287e 100644 --- a/hawc/apps/summary/templates/summary/visual_form.html +++ b/hawc/apps/summary/templates/summary/visual_form.html @@ -1,11 +1,6 @@ {% extends 'assessment-rooted.html' %} {% load crispy_forms_tags %} -{% load selectable_tags %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} {% block content %}