From d3f8e0dad013165c497c9cc360149f55c871f1c2 Mon Sep 17 00:00:00 2001 From: casey1173 Date: Sat, 31 Aug 2024 20:34:44 -0400 Subject: [PATCH 1/7] label detail page --- hawc/apps/assessment/managers.py | 4 ++++ hawc/apps/assessment/models.py | 13 +++++++++++ .../fragments/label_indicators.html | 2 +- .../assessment/labeleditem_list.html | 22 +++++++++++++++++++ hawc/apps/assessment/urls.py | 5 +++++ hawc/apps/assessment/views.py | 19 ++++++++++++++++ hawc/apps/common/templatetags/hawc.py | 5 +++++ .../templates/summary/visual_list.html | 6 ++--- hawc/static/css/hawc.css | 14 ++++++++++++ 9 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 hawc/apps/assessment/templates/assessment/labeleditem_list.html diff --git a/hawc/apps/assessment/managers.py b/hawc/apps/assessment/managers.py index 8d9515af6f..62e40c86d0 100644 --- a/hawc/apps/assessment/managers.py +++ b/hawc/apps/assessment/managers.py @@ -367,3 +367,7 @@ class LabelManager(MP_NodeManager): def get_applied(self, _object): content_type, object_id = object_to_content_object(_object) return self.filter(items__content_type=content_type, items__object_id=object_id) + + +class LabeledItemManager(BaseManager): + assessment_relation = "label__assessment" diff --git a/hawc/apps/assessment/models.py b/hawc/apps/assessment/models.py index 13451bc4f2..7112c66c71 100644 --- a/hawc/apps/assessment/models.py +++ b/hawc/apps/assessment/models.py @@ -1344,9 +1344,14 @@ class Label(AssessmentRootMixin, MP_Node): cache_template_taglist = "assessment.label.labellist.assessment-{0}" cache_template_tagtree = "assessment.label.labeltree.assessment-{0}" + BREADCRUMB_PARENT = "assessment" + class Meta: constraints = [models.UniqueConstraint(fields=["assessment", "name"], name="label_name")] + def __str__(self): + return self.name + @classmethod def create_root(cls, assessment_id, **kwargs): """ @@ -1372,6 +1377,9 @@ def get_nested_name(self) -> str: else: return f"{'━ ' * (self.depth - 1)}{self.name}" + def get_labeled_items_url(self): + return reverse("assessment:labeled-items", args=[self.pk]) + def get_absolute_url(self): return reverse("assessment:label-htmx", args=[self.pk, "read"]) @@ -1383,6 +1391,8 @@ def get_delete_url(self): class LabeledItem(models.Model): + objects = managers.LabeledItemManager() + label = models.ForeignKey(Label, models.CASCADE, related_name="items") content_type = models.ForeignKey(ContentType, models.CASCADE) object_id = models.PositiveIntegerField() @@ -1390,6 +1400,9 @@ class LabeledItem(models.Model): created = models.DateTimeField(auto_now_add=True) last_updated = models.DateTimeField(auto_now=True) + def __str__(self): + return f"{self.label} on {self.content_object}" + reversion.register(DSSTox) reversion.register(Assessment) diff --git a/hawc/apps/assessment/templates/assessment/fragments/label_indicators.html b/hawc/apps/assessment/templates/assessment/fragments/label_indicators.html index 547e3d4906..63f84e347d 100644 --- a/hawc/apps/assessment/templates/assessment/fragments/label_indicators.html +++ b/hawc/apps/assessment/templates/assessment/fragments/label_indicators.html @@ -5,7 +5,7 @@ hx-swap-oob="outerHTML" > {% for label in labels %} -
{{label.name}}
+ {{label.name}} {% endfor %} {% else %} diff --git a/hawc/apps/assessment/templates/assessment/labeleditem_list.html b/hawc/apps/assessment/templates/assessment/labeleditem_list.html new file mode 100644 index 0000000000..f09c9e6347 --- /dev/null +++ b/hawc/apps/assessment/templates/assessment/labeleditem_list.html @@ -0,0 +1,22 @@ +{% extends 'assessment-rooted.html' %} + +{% block content %} + +

Objects with
{{object.name}}
label

+ +
+{% for object in object_list %} + +
{{object.content_object|verbose_name|title}}
+
{{object.content_object}} + + {% for labeled_item in object.content_object.labels.all %} +
{{labeled_item.label.name}}
+ {% endfor %} +
+
+{% empty %} +{% alert type="warning" classes="my-0" %} No objects found.{% endalert %} +{% endfor %} +
+{% endblock %} \ No newline at end of file diff --git a/hawc/apps/assessment/urls.py b/hawc/apps/assessment/urls.py index 1f865b25a4..c92ba52ed9 100644 --- a/hawc/apps/assessment/urls.py +++ b/hawc/apps/assessment/urls.py @@ -140,6 +140,11 @@ name="publish-update", ), # assessment labels + path( + "labeled-items//", + views.LabeledItemList.as_view(), + name="labeled-items", + ), path( "/labels/", views.LabelList.as_view(), diff --git a/hawc/apps/assessment/views.py b/hawc/apps/assessment/views.py index 5e7296052c..1690647917 100644 --- a/hawc/apps/assessment/views.py +++ b/hawc/apps/assessment/views.py @@ -930,6 +930,25 @@ def get_context_data(self, **kwargs): return context +class LabeledItemList(BaseDetail): + parent_model = models.Assessment + model = models.Label + assessment_permission = constants.AssessmentViewPermissions.TEAM_MEMBER + template_name = "assessment/labeleditem_list.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update( + object_list=models.LabeledItem.objects.filter( + label__path__startswith=self.object.path, + label__depth__gte=self.object.depth, + ) + .order_by("content_type", "object_id") + .distinct("content_type", "object_id") + ) + return context + + class LabelViewSet(HtmxViewSet): actions = {"create", "read", "update", "delete"} parent_model = models.Assessment diff --git a/hawc/apps/common/templatetags/hawc.py b/hawc/apps/common/templatetags/hawc.py index a0ec691bcc..a8d81f9096 100644 --- a/hawc/apps/common/templatetags/hawc.py +++ b/hawc/apps/common/templatetags/hawc.py @@ -22,6 +22,11 @@ def get(dictionary: dict, key: str): return dictionary.get(key) +@register.filter +def verbose_name(instance): + return instance._meta.verbose_name + + @register.simple_tag def audit_url(object): ct = ContentType.objects.get_for_model(object.__class__) diff --git a/hawc/apps/summary/templates/summary/visual_list.html b/hawc/apps/summary/templates/summary/visual_list.html index c121e92409..a31808708d 100644 --- a/hawc/apps/summary/templates/summary/visual_list.html +++ b/hawc/apps/summary/templates/summary/visual_list.html @@ -27,13 +27,13 @@

Available visualizations

{{visual.title}} {% for labeled_item in visual.visible_labels %} -
{{labeled_item.label.name}}
+ {{labeled_item.label.name}} {% endfor %} {% for labeled_item in visual.datapivotquery.visible_query_labels %} -
{{labeled_item.label.name}}
+ {{labeled_item.label.name}} {% endfor %} {% for labeled_item in visual.datapivotupload.visible_upload_labels %} -
{{labeled_item.label.name}}
+ {{labeled_item.label.name}} {% endfor %} {% if not visual.published %} diff --git a/hawc/static/css/hawc.css b/hawc/static/css/hawc.css index fa927d17d5..fa1aefdd08 100644 --- a/hawc/static/css/hawc.css +++ b/hawc/static/css/hawc.css @@ -59,6 +59,7 @@ input[type="submit"].btn, section button.btn, section input[type="button"].btn, overflow: hidden; font-size: 0.85rem; padding: .2rem 1rem .2rem .4rem; + font-family: Source Sans Pro Web, "Noto Sans Arabic", "Noto Sans BN homepage", "Noto Sans GU homepage", "Noto Sans KR homepage", "Noto Sans SC homepage", "Noto Sans BN", "Noto Sans GU", "Noto Sans KR", "Noto Sans SC", "Noto Sans TC", "Helvetica Neue", Helvetica, Arial, sans; clip-path: polygon( 0% 0, calc(100% - 12px) 0, @@ -143,6 +144,19 @@ tfoot > tr > td { .dark-link, .dark-link:visited { color: #495057; } +.clickable, .clickable .bg-light { + transition: background-color 0.20s ease; + cursor: pointer; +} +.clickable:active { + background-color: #e3e4e5 !important; +} +.bg-light.clickable:hover, .clickable:hover .bg-light { + background-color: #e6e7e8 !important; +} +.bg-light.clickable:active, .clickable:active .bg-light { + background-color: #d4d5d6 !important; +} .showOptsCaret:hover .optsCaret { opacity: 1; -webkit-transition: all 0.5s ease-in-out; From ec48125f7af8efcf04843b38edf843ace1875a5a Mon Sep 17 00:00:00 2001 From: casey1173 Date: Sat, 31 Aug 2024 20:37:46 -0400 Subject: [PATCH 2/7] lint --- .../assessment/labeleditem_list.html | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/hawc/apps/assessment/templates/assessment/labeleditem_list.html b/hawc/apps/assessment/templates/assessment/labeleditem_list.html index f09c9e6347..d09f5de0a2 100644 --- a/hawc/apps/assessment/templates/assessment/labeleditem_list.html +++ b/hawc/apps/assessment/templates/assessment/labeleditem_list.html @@ -2,21 +2,21 @@ {% block content %} -

Objects with
{{object.name}}
label

+

Objects with
{{object.name}}
label

- {% endblock %} \ No newline at end of file From 2bbcd12f35422edb6c5492b971185b65bc4448e2 Mon Sep 17 00:00:00 2001 From: casey1173 Date: Thu, 5 Sep 2024 13:04:55 -0400 Subject: [PATCH 3/7] change to filtered list view --- hawc/apps/assessment/autocomplete.py | 13 ++++ hawc/apps/assessment/filterset.py | 69 ++++++++++++++++++- hawc/apps/assessment/models.py | 3 - .../fragments/label_indicators.html | 4 +- .../assessment/labeleditem_list.html | 25 +++++-- hawc/apps/assessment/urls.py | 2 +- hawc/apps/assessment/views.py | 25 ++++--- .../common/crispy_layout_filter_field.html | 4 +- hawc/apps/summary/models.py | 8 +-- .../templates/summary/summarytable_list.html | 2 +- .../templates/summary/visual_list.html | 20 +++--- hawc/static/css/hawc.css | 30 ++++++-- 12 files changed, 164 insertions(+), 41 deletions(-) diff --git a/hawc/apps/assessment/autocomplete.py b/hawc/apps/assessment/autocomplete.py index 9178486bb1..d0dcd48299 100644 --- a/hawc/apps/assessment/autocomplete.py +++ b/hawc/apps/assessment/autocomplete.py @@ -64,3 +64,16 @@ class AssessmentDetailAutocomplete(BaseAutocomplete): @register class AssessmentValueAutocomplete(BaseAutocomplete): model = models.AssessmentValue + + +@register +class LabelAutocomplete(BaseAutocomplete): + model = models.Label + filter_fields = ["assessment_id"] + + @classmethod + def get_base_queryset(cls, filters: dict | None = None): + return super().get_base_queryset(filters).filter(depth__gte=2) + + def get_result_label(self, result): + return result.get_nested_name()[1:] diff --git a/hawc/apps/assessment/filterset.py b/hawc/apps/assessment/filterset.py index 8b50258089..6fecb26bac 100644 --- a/hawc/apps/assessment/filterset.py +++ b/hawc/apps/assessment/filterset.py @@ -1,10 +1,12 @@ import django_filters as df from django import forms -from django.db.models import Q +from django.db.models import Q, TextField +from django.db.models.functions import Concat from django.urls import reverse from ..common.filterset import ( ArrowOrderingFilter, + AutocompleteModelMultipleChoiceFilter, BaseFilterSet, ExpandableFilterForm, InlineFilterForm, @@ -12,6 +14,7 @@ from ..common.helper import new_window_a from ..myuser.models import HAWCUser from . import models +from .autocomplete import LabelAutocomplete from .constants import PublishedStatus @@ -165,3 +168,67 @@ def create_form(self): class EffectTagFilterSet(df.FilterSet): name = df.CharFilter(lookup_expr="icontains") + + +class LabeledItemFilterset(BaseFilterSet): + name = df.CharFilter( + method="filter_title", + label="Object Name", + help_text="Filter by object name", + ) + label = AutocompleteModelMultipleChoiceFilter( + autocomplete_class=LabelAutocomplete, + method="filter_labels", + ) + + class Meta: + model = models.LabeledItem + form = InlineFilterForm + fields = ("name", "label") + + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + return queryset.filter(label__assessment=self.assessment) + + def filter_title(self, queryset, name, value): + if not value: + return queryset + query = ( + Q(summary_table__title__icontains=value) + | Q(visual__title__icontains=value) + | Q(datapivot_query__title__icontains=value) + | Q(datapivot_upload__title__icontains=value) + ) + return queryset.filter(query) + + def filter_labels(self, queryset, name, value): + if not value: + return queryset + queryset = queryset.annotate( + object_info=Concat("content_type", "object_id", output_field=TextField()), + ) # annotate each in the list with content_type + object_id to create a unique field + matching_objects = None + for label in value: + if ( + not matching_objects or len(matching_objects) > 0 + ): # quit early if we run out of matching objects + objects_with_label = ( + queryset.filter( + label__path__startswith=label.path, label__depth__gte=label.depth + ) + .values_list("object_info", flat=True) + .distinct() + ) + matching_objects = ( + matching_objects.intersection(objects_with_label) + if matching_objects + else set(objects_with_label) + ) # only filtering for objects matching all labels + return queryset.filter(object_info__in=matching_objects) + + def create_form(self): + form = super().create_form() + form.fields["label"].set_filters({"assessment_id": self.assessment.id}) + form.fields["label"].widget.attrs.update({"data-placeholder": "Filter by labels"}) + form.fields["label"].widget.attrs["size"] = 1 + return form diff --git a/hawc/apps/assessment/models.py b/hawc/apps/assessment/models.py index 7112c66c71..3755b6e209 100644 --- a/hawc/apps/assessment/models.py +++ b/hawc/apps/assessment/models.py @@ -1377,9 +1377,6 @@ def get_nested_name(self) -> str: else: return f"{'━ ' * (self.depth - 1)}{self.name}" - def get_labeled_items_url(self): - return reverse("assessment:labeled-items", args=[self.pk]) - def get_absolute_url(self): return reverse("assessment:label-htmx", args=[self.pk, "read"]) diff --git a/hawc/apps/assessment/templates/assessment/fragments/label_indicators.html b/hawc/apps/assessment/templates/assessment/fragments/label_indicators.html index 63f84e347d..046b56fa6c 100644 --- a/hawc/apps/assessment/templates/assessment/fragments/label_indicators.html +++ b/hawc/apps/assessment/templates/assessment/fragments/label_indicators.html @@ -1,11 +1,11 @@ {% if oob %}
{% for label in labels %} - {{label.name}} + {{label.name}} {% endfor %}
{% else %} diff --git a/hawc/apps/assessment/templates/assessment/labeleditem_list.html b/hawc/apps/assessment/templates/assessment/labeleditem_list.html index d09f5de0a2..8a55b822c2 100644 --- a/hawc/apps/assessment/templates/assessment/labeleditem_list.html +++ b/hawc/apps/assessment/templates/assessment/labeleditem_list.html @@ -2,16 +2,21 @@ {% block content %} -

Objects with
{{object.name}}
label

+
+

Labeled Objects in {{assessment}}

+ Manage Labels +
+ {% include 'common/inline_filter_form.html' %}
{% for object in object_list %}
{{object.content_object|verbose_name|title}}
-
{{object.content_object}} - +
+
{{object.content_object}}
+ {% for labeled_item in object.content_object.labels.all %} -
{{labeled_item.label.name}}
+
{{labeled_item.label.name}}
{% endfor %}
@@ -19,4 +24,16 @@

Objects with
+{% endblock %} + +{% block extrajs %} + {% endblock %} \ No newline at end of file diff --git a/hawc/apps/assessment/urls.py b/hawc/apps/assessment/urls.py index c92ba52ed9..ea79111b23 100644 --- a/hawc/apps/assessment/urls.py +++ b/hawc/apps/assessment/urls.py @@ -141,7 +141,7 @@ ), # assessment labels path( - "labeled-items//", + "/labeled-items/", views.LabeledItemList.as_view(), name="labeled-items", ), diff --git a/hawc/apps/assessment/views.py b/hawc/apps/assessment/views.py index 1690647917..d1ef1fdacc 100644 --- a/hawc/apps/assessment/views.py +++ b/hawc/apps/assessment/views.py @@ -930,23 +930,27 @@ def get_context_data(self, **kwargs): return context -class LabeledItemList(BaseDetail): +class LabeledItemList(BaseFilterList): parent_model = models.Assessment - model = models.Label + model = models.LabeledItem assessment_permission = constants.AssessmentViewPermissions.TEAM_MEMBER template_name = "assessment/labeleditem_list.html" + filterset_class = filterset.LabeledItemFilterset - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context.update( - object_list=models.LabeledItem.objects.filter( - label__path__startswith=self.object.path, - label__depth__gte=self.object.depth, - ) + def get_filterset_form_kwargs(self): + return dict( + main_field="name", + appended_fields=["label"], + ) + + def get_queryset(self): + return ( + super() + .get_queryset() + .select_related("label") .order_by("content_type", "object_id") .distinct("content_type", "object_id") ) - return context class LabelViewSet(HtmxViewSet): @@ -1052,6 +1056,7 @@ def label_indicators(self, request: HttpRequest, *args, **kwargs): context = dict( content_type=self.content_type, object_id=self.object_id, + assessment=self.assessment, labels=labels, oob=True, ) diff --git a/hawc/apps/common/templates/common/crispy_layout_filter_field.html b/hawc/apps/common/templates/common/crispy_layout_filter_field.html index 64a5378b21..5361507876 100644 --- a/hawc/apps/common/templates/common/crispy_layout_filter_field.html +++ b/hawc/apps/common/templates/common/crispy_layout_filter_field.html @@ -29,9 +29,9 @@
{{field.widget}} {% if field.errors %} - {% crispy_field field 'class' 'custom-select form-sm-field is-invalid' %} + {% crispy_field field 'class' 'custom-select form-sm-field is-invalid' 'placeholder' field.help_text %} {% else %} - {% crispy_field field 'class' 'custom-select form-sm-field' %} + {% crispy_field field 'class' 'custom-select form-sm-field' 'placeholder' field.help_text %} {% endif %}
{% endif %} diff --git a/hawc/apps/summary/models.py b/hawc/apps/summary/models.py index f2aa965ec8..1491c224bc 100644 --- a/hawc/apps/summary/models.py +++ b/hawc/apps/summary/models.py @@ -71,7 +71,7 @@ class SummaryTable(models.Model): help_text="For assessments marked for public viewing, mark table to be viewable by public", ) caption = models.TextField(blank=True, validators=[validate_html_tags, validate_hyperlinks]) - labels = GenericRelation(LabeledItem) + labels = GenericRelation(LabeledItem, related_query_name="summary_table") created = models.DateTimeField(auto_now_add=True) last_updated = models.DateTimeField(auto_now=True) @@ -230,7 +230,7 @@ class Visual(models.Model): ) created = models.DateTimeField(auto_now_add=True) last_updated = models.DateTimeField(auto_now=True) - labels = GenericRelation(LabeledItem) + labels = GenericRelation(LabeledItem, related_query_name="visual") BREADCRUMB_PARENT = "assessment" @@ -619,7 +619,7 @@ class DataPivotUpload(DataPivot): max_length=64, blank=True, ) - labels = GenericRelation(LabeledItem) + labels = GenericRelation(LabeledItem, related_query_name="datapivot_upload") @property def visual_type(self): @@ -662,7 +662,7 @@ class DataPivotQuery(DataPivot): "creating one plot similar, but not identical, dose-units.", ) prefilters = models.JSONField(default=dict) - labels = GenericRelation(LabeledItem) + labels = GenericRelation(LabeledItem, related_query_name="datapivot_query") def clean(self): count = self.get_queryset().count() diff --git a/hawc/apps/summary/templates/summary/summarytable_list.html b/hawc/apps/summary/templates/summary/summarytable_list.html index 14a98e2074..26001a35c8 100644 --- a/hawc/apps/summary/templates/summary/summarytable_list.html +++ b/hawc/apps/summary/templates/summary/summarytable_list.html @@ -28,7 +28,7 @@

Available tables

{{object.title}} {% for labeled_item in object.visible_labels %} -
{{labeled_item.label.name}}
+ {{labeled_item.label.name}} {% endfor %}
{% if obj_perms.edit and not object.published %} diff --git a/hawc/apps/summary/templates/summary/visual_list.html b/hawc/apps/summary/templates/summary/visual_list.html index a31808708d..23fae39395 100644 --- a/hawc/apps/summary/templates/summary/visual_list.html +++ b/hawc/apps/summary/templates/summary/visual_list.html @@ -26,15 +26,17 @@

Available visualizations

{{visual.title}} - {% for labeled_item in visual.visible_labels %} - {{labeled_item.label.name}} - {% endfor %} - {% for labeled_item in visual.datapivotquery.visible_query_labels %} - {{labeled_item.label.name}} - {% endfor %} - {% for labeled_item in visual.datapivotupload.visible_upload_labels %} - {{labeled_item.label.name}} - {% endfor %} +
+ {% for labeled_item in visual.visible_labels %} + {{labeled_item.label.name}} + {% endfor %} + {% for labeled_item in visual.datapivotquery.visible_query_labels %} + {{labeled_item.label.name}} + {% endfor %} + {% for labeled_item in visual.datapivotupload.visible_upload_labels %} + {{labeled_item.label.name}} + {% endfor %} +
{% if not visual.published %} {% endif %} diff --git a/hawc/static/css/hawc.css b/hawc/static/css/hawc.css index fa1aefdd08..4681ad2765 100644 --- a/hawc/static/css/hawc.css +++ b/hawc/static/css/hawc.css @@ -67,7 +67,22 @@ input[type="submit"].btn, section button.btn, section input[type="button"].btn, 100% 52%, calc(100% - 12px) 100%, 0% 100% - ) + ); + -webkit-transition: all 0.1s ease-in; + transition: all 0.1s ease-in; +} +.label:hover{ + -webkit-transform: scale(1.1); + transform: scale(1.1); + text-decoration: none; + cursor: pointer; +} + +.clickable:hover:has(.label:hover) { + background-color: unset !important; +} +.clickable:hover:has(.label:hover) .bg-light { + background-color: #f8f9fa !important; } .unpublishedBadge+.actionsMenu, .unpublishedBadge+button.close { @@ -753,15 +768,22 @@ a.outline-btn:hover, .dropdown.outline-btn:hover, .btn.outline-btn:hover { padding: 0.2rem 0.75rem; } -div.input-group span.autocompletetextwidget { - height: 37.5px; +div.input-group span.autocompletetextwidget, div.input-group span.autocompleteselectmultiplewidget { + height: 38px; line-height: 1.7; + border-radius: 0px; + min-width: 250px; } -div.input-group-append select { +div.input-group-append select, div.input-group-append span.select2 { height: 100%; } +div.input-group-append input.select2-search__field { + font-size: 1rem; + height: 38px !important; +} + /* BMD APPLICATION STYLES */ /* BMD output table */ From 56f6effb2238b310461c26730670a1cefa9a9ede Mon Sep 17 00:00:00 2001 From: casey1173 Date: Fri, 6 Sep 2024 12:23:25 -0400 Subject: [PATCH 4/7] update visual list filter widgets, add permissions --- hawc/apps/assessment/autocomplete.py | 2 +- hawc/apps/assessment/filterset.py | 6 +- hawc/apps/assessment/forms.py | 3 +- .../fragments/label_indicators.html | 2 +- .../assessment/fragments/label_row.html | 2 +- .../templates/assessment/label_list.html | 5 + .../assessment/labeleditem_list.html | 30 +++-- hawc/apps/assessment/views.py | 5 +- hawc/apps/summary/filterset.py | 104 ++++++++++-------- hawc/apps/summary/views.py | 2 +- 10 files changed, 99 insertions(+), 62 deletions(-) diff --git a/hawc/apps/assessment/autocomplete.py b/hawc/apps/assessment/autocomplete.py index d0dcd48299..e6ef66753b 100644 --- a/hawc/apps/assessment/autocomplete.py +++ b/hawc/apps/assessment/autocomplete.py @@ -69,7 +69,7 @@ class AssessmentValueAutocomplete(BaseAutocomplete): @register class LabelAutocomplete(BaseAutocomplete): model = models.Label - filter_fields = ["assessment_id"] + filter_fields = ["assessment_id", "published"] @classmethod def get_base_queryset(cls, filters: dict | None = None): diff --git a/hawc/apps/assessment/filterset.py b/hawc/apps/assessment/filterset.py index 6fecb26bac..42a09b5d38 100644 --- a/hawc/apps/assessment/filterset.py +++ b/hawc/apps/assessment/filterset.py @@ -228,7 +228,11 @@ def filter_labels(self, queryset, name, value): def create_form(self): form = super().create_form() - form.fields["label"].set_filters({"assessment_id": self.assessment.id}) form.fields["label"].widget.attrs.update({"data-placeholder": "Filter by labels"}) + form.fields["label"].set_filters( + {"assessment_id": self.assessment.id, "published": True} + if not self.perms["edit"] + else {"assessment_id": self.assessment.id} + ) form.fields["label"].widget.attrs["size"] = 1 return form diff --git a/hawc/apps/assessment/forms.py b/hawc/apps/assessment/forms.py index 6a9881e10d..442bc850b7 100644 --- a/hawc/apps/assessment/forms.py +++ b/hawc/apps/assessment/forms.py @@ -678,7 +678,8 @@ class Meta: def __init__(self, *args, **kwargs): assessment = kwargs.pop("assessment", None) - super().__init__(*args, **kwargs) + prefix = kwargs.get("instance").pk if "instance" in kwargs else "-1" + super().__init__(*args, prefix=prefix, **kwargs) if assessment: self.instance.assessment = assessment if self.instance.pk is not None: diff --git a/hawc/apps/assessment/templates/assessment/fragments/label_indicators.html b/hawc/apps/assessment/templates/assessment/fragments/label_indicators.html index 046b56fa6c..7d2c32359f 100644 --- a/hawc/apps/assessment/templates/assessment/fragments/label_indicators.html +++ b/hawc/apps/assessment/templates/assessment/fragments/label_indicators.html @@ -1,6 +1,6 @@ {% if oob %}
diff --git a/hawc/apps/assessment/templates/assessment/fragments/label_row.html b/hawc/apps/assessment/templates/assessment/fragments/label_row.html index 2eb6cca71a..58be2abbcb 100644 --- a/hawc/apps/assessment/templates/assessment/fragments/label_row.html +++ b/hawc/apps/assessment/templates/assessment/fragments/label_row.html @@ -10,7 +10,7 @@
{% widthratio object.depth 10 20 as marginLeft %} -

{{object.name}}

+

{{object.name}}

diff --git a/hawc/apps/assessment/templates/assessment/label_list.html b/hawc/apps/assessment/templates/assessment/label_list.html index 0929ce2f3f..b27c3cbf93 100644 --- a/hawc/apps/assessment/templates/assessment/label_list.html +++ b/hawc/apps/assessment/templates/assessment/label_list.html @@ -26,6 +26,11 @@

Labels

{% endblock %} \ No newline at end of file diff --git a/hawc/apps/assessment/templates/assessment/labeleditem_list.html b/hawc/apps/assessment/templates/assessment/labeleditem_list.html index 8a55b822c2..5a322dc910 100644 --- a/hawc/apps/assessment/templates/assessment/labeleditem_list.html +++ b/hawc/apps/assessment/templates/assessment/labeleditem_list.html @@ -4,22 +4,30 @@

Labeled Objects in {{assessment}}

- Manage Labels + {% if obj_perms.edit %} + Manage Labels + {% endif %}
{% include 'common/inline_filter_form.html' %}
{% for object in object_list %} - -
{{object.content_object|verbose_name|title}}
-
-
{{object.content_object}}
- - {% for labeled_item in object.content_object.labels.all %} -
{{labeled_item.label.name}}
- {% endfor %} -
-
+ {% if object.content_object.published or obj_perms.edit %} + +
{{object.content_object|verbose_name|title}}
+
+
{{object.content_object}}
+ {% if obj_perms.edit %} + + {% endif %} + {% for labeled_item in object.content_object.labels.all %} + {% if labeled_item.label.published or obj_perms.edit %} +
{{labeled_item.label.name}}
+ {% endif %} + {% endfor %} +
+
+ {% endif %} {% empty %} {% alert type="warning" classes="my-0" %} No objects found.{% endalert %} {% endfor %} diff --git a/hawc/apps/assessment/views.py b/hawc/apps/assessment/views.py index d1ef1fdacc..4a5c4bce63 100644 --- a/hawc/apps/assessment/views.py +++ b/hawc/apps/assessment/views.py @@ -933,7 +933,6 @@ def get_context_data(self, **kwargs): class LabeledItemList(BaseFilterList): parent_model = models.Assessment model = models.LabeledItem - assessment_permission = constants.AssessmentViewPermissions.TEAM_MEMBER template_name = "assessment/labeleditem_list.html" filterset_class = filterset.LabeledItemFilterset @@ -1023,7 +1022,9 @@ def dispatch(self, request, *args, **kwargs): return handler(request, *args, **kwargs) def label(self, request: HttpRequest, *args, **kwargs): - context = dict(content_type=self.content_type, object_id=self.object_id) + context = dict( + content_type=self.content_type, object_id=self.object_id, assessment=self.assessment + ) if request.method == "GET": form = forms.LabelItemForm( data=dict( diff --git a/hawc/apps/summary/filterset.py b/hawc/apps/summary/filterset.py index db3a9f6c84..95f99e7d4c 100644 --- a/hawc/apps/summary/filterset.py +++ b/hawc/apps/summary/filterset.py @@ -2,9 +2,14 @@ from django.contrib.contenttypes.models import ContentType from django.db.models import Exists, OuterRef, Prefetch, Q +from ..assessment.autocomplete import LabelAutocomplete from ..assessment.constants import RobName from ..assessment.models import Label, LabeledItem -from ..common.filterset import BaseFilterSet, InlineFilterForm +from ..common.filterset import ( + AutocompleteModelMultipleChoiceFilter, + BaseFilterSet, + InlineFilterForm, +) from . import constants, models @@ -21,11 +26,9 @@ class VisualFilterSet(BaseFilterSet): help_text="Type of visualization to display", empty_label="All visual types", ) - label = df.ChoiceFilter( - method="filter_label", - label="Applied label", - help_text="Visualizations with label applied", - empty_label="All labels", + label = AutocompleteModelMultipleChoiceFilter( + autocomplete_class=LabelAutocomplete, + method="filter_labels", ) published = df.ChoiceFilter( choices=[(True, "Published only"), (False, "Unpublished only")], @@ -59,19 +62,20 @@ def filter_queryset(self, queryset): query &= Q(published=True) return queryset.filter(query).order_by("id") - def filter_label(self, queryset, name, value): + def filter_labels(self, queryset, name, value): if not value: return queryset - label = Label.objects.get(pk=value) content_type = ContentType.objects.get_for_model(models.Visual) - subquery = LabeledItem.objects.filter( - **(dict(label__published=True) if not self.perms["edit"] else dict()), - label__path__startswith=label.path, - label__depth__gte=label.depth, - content_type=content_type, - object_id=OuterRef("pk"), - ) - return queryset.filter(Exists(subquery)) + for label in value: + subquery = LabeledItem.objects.filter( + **(dict(label__published=True) if not self.perms["edit"] else dict()), + label__path__startswith=label.path, + label__depth__gte=label.depth, + content_type=content_type, + object_id=OuterRef("pk"), + ) + queryset = queryset.filter(Exists(subquery)) + return queryset def get_type_choices(self): choices = ( @@ -98,7 +102,15 @@ def get_label_choices(self): def create_form(self): form = super().create_form() form.fields["type"].choices = self.get_type_choices() - form.fields["label"].choices = self.get_label_choices() + form.fields["label"].set_filters( + {"assessment_id": self.assessment.id, "published": True} + if not self.perms["edit"] + else {"assessment_id": self.assessment.id} + ) + form.fields["label"].widget.attrs.update( + {"data-placeholder": "Visualizations with label applied"} + ) + form.fields["label"].widget.attrs["size"] = 1 return form @@ -136,21 +148,22 @@ def annotate_queryset(self, queryset): def filter_queryset(self, queryset): return super().filter_queryset(queryset).select_related("datapivotquery", "datapivotupload") - def filter_label(self, queryset, name, value): + def filter_labels(self, queryset, name, value): if not value: return queryset - label = Label.objects.get(pk=value) content_types = ContentType.objects.get_for_models( models.DataPivot, models.DataPivotQuery, models.DataPivotUpload ).values() - subquery = LabeledItem.objects.filter( - **(dict(label__published=True) if not self.perms["edit"] else dict()), - label__path__startswith=label.path, - label__depth__gte=label.depth, - content_type__in=content_types, - object_id=OuterRef("pk"), - ) - return queryset.filter(Exists(subquery)) + for label in value: + subquery = LabeledItem.objects.filter( + **(dict(label__published=True) if not self.perms["edit"] else dict()), + label__path__startswith=label.path, + label__depth__gte=label.depth, + content_type__in=content_types, + object_id=OuterRef("pk"), + ) + queryset = queryset.filter(Exists(subquery)) + return queryset def get_type_choices(self): choice_options = ( @@ -188,11 +201,9 @@ class SummaryTableFilterSet(BaseFilterSet): help_text="Type of summary table to display", empty_label="All table types", ) - label = df.ChoiceFilter( - method="filter_label", - label="Applied label", - help_text="Visualizations with label applied", - empty_label="All labels", + label = AutocompleteModelMultipleChoiceFilter( + autocomplete_class=LabelAutocomplete, + method="filter_labels", ) published = df.ChoiceFilter( choices=[(True, "Published only"), (False, "Unpublished only")], @@ -222,19 +233,20 @@ def filter_queryset(self, queryset): query &= Q(published=True) return queryset.filter(query).order_by("id") - def filter_label(self, queryset, name, value): + def filter_labels(self, queryset, name, value): if not value: return queryset - label = Label.objects.get(pk=value) content_type = ContentType.objects.get_for_model(models.SummaryTable) - subquery = LabeledItem.objects.filter( - **(dict(label__published=True) if not self.perms["edit"] else dict()), - label__path__startswith=label.path, - label__depth__gte=label.depth, - content_type=content_type, - object_id=OuterRef("pk"), - ) - return queryset.filter(Exists(subquery)) + for label in value: + subquery = LabeledItem.objects.filter( + **(dict(label__published=True) if not self.perms["edit"] else dict()), + label__path__startswith=label.path, + label__depth__gte=label.depth, + content_type=content_type, + object_id=OuterRef("pk"), + ) + queryset = queryset.filter(Exists(subquery)) + return queryset def get_label_choices(self): labels = Label.get_assessment_qs(self.assessment.pk) @@ -251,6 +263,12 @@ def create_form(self): ) choices = [constants.TableType(choice) for choice in sorted(set(choices))] form.fields["type"].choices = [(choice.value, choice.label) for choice in choices] - form.fields["label"].choices = self.get_label_choices() + form.fields["label"].set_filters( + {"assessment_id": self.assessment.id, "published": True} + if not self.perms["edit"] + else {"assessment_id": self.assessment.id} + ) + form.fields["label"].widget.attrs.update({"data-placeholder": "Tables with label applied"}) + form.fields["label"].widget.attrs["size"] = 1 return form diff --git a/hawc/apps/summary/views.py b/hawc/apps/summary/views.py index 597bb2d3b9..d59fb9e1cd 100644 --- a/hawc/apps/summary/views.py +++ b/hawc/apps/summary/views.py @@ -68,7 +68,7 @@ def get_filterset_form_kwargs(self): return dict( main_field="title", appended_fields=["type", "label"], - dynamic_fields=["title", "type"], + dynamic_fields=["title", "type", "label"], ) From 46df5e8f93324eedcdbc0298484e98ecea62ebde Mon Sep 17 00:00:00 2001 From: casey1173 Date: Fri, 6 Sep 2024 13:17:53 -0400 Subject: [PATCH 5/7] add prefetching, change template tag name, consolidate label html --- hawc/apps/assessment/models.py | 2 -- .../templates/assessment/fragments/label.html | 5 +++++ .../assessment/fragments/label_indicators.html | 2 +- .../templates/assessment/labeleditem_list.html | 8 ++++---- hawc/apps/assessment/views.py | 13 +++++++++++++ hawc/apps/common/templatetags/hawc.py | 2 +- .../apps/summary/templates/summary/visual_list.html | 6 +++--- 7 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 hawc/apps/assessment/templates/assessment/fragments/label.html diff --git a/hawc/apps/assessment/models.py b/hawc/apps/assessment/models.py index 3755b6e209..e5766e32db 100644 --- a/hawc/apps/assessment/models.py +++ b/hawc/apps/assessment/models.py @@ -1344,8 +1344,6 @@ class Label(AssessmentRootMixin, MP_Node): cache_template_taglist = "assessment.label.labellist.assessment-{0}" cache_template_tagtree = "assessment.label.labeltree.assessment-{0}" - BREADCRUMB_PARENT = "assessment" - class Meta: constraints = [models.UniqueConstraint(fields=["assessment", "name"], name="label_name")] diff --git a/hawc/apps/assessment/templates/assessment/fragments/label.html b/hawc/apps/assessment/templates/assessment/fragments/label.html new file mode 100644 index 0000000000..c879903b8c --- /dev/null +++ b/hawc/apps/assessment/templates/assessment/fragments/label.html @@ -0,0 +1,5 @@ +{% if anchor_tag %} + {{label.name}} +{% else %} +
{{label.name}}
+{% endif %} \ No newline at end of file diff --git a/hawc/apps/assessment/templates/assessment/fragments/label_indicators.html b/hawc/apps/assessment/templates/assessment/fragments/label_indicators.html index 7d2c32359f..a825579754 100644 --- a/hawc/apps/assessment/templates/assessment/fragments/label_indicators.html +++ b/hawc/apps/assessment/templates/assessment/fragments/label_indicators.html @@ -5,7 +5,7 @@ hx-swap-oob="outerHTML" > {% for label in labels %} - {{label.name}} + {% include "assessment/fragments/label.html" with big=True anchor_tag=True %} {% endfor %}
{% else %} diff --git a/hawc/apps/assessment/templates/assessment/labeleditem_list.html b/hawc/apps/assessment/templates/assessment/labeleditem_list.html index 5a322dc910..1ba7b19d6c 100644 --- a/hawc/apps/assessment/templates/assessment/labeleditem_list.html +++ b/hawc/apps/assessment/templates/assessment/labeleditem_list.html @@ -14,15 +14,15 @@

Labeled Objects in {{assessment} {% for object in object_list %} {% if object.content_object.published or obj_perms.edit %} -
{{object.content_object|verbose_name|title}}
+
{{object.content_object|model_verbose_name|title}}
{{object.content_object}}
{% if obj_perms.edit %} - + {% endif %} {% for labeled_item in object.content_object.labels.all %} {% if labeled_item.label.published or obj_perms.edit %} -
{{labeled_item.label.name}}
+ {% include "assessment/fragments/label.html" with label=labeled_item.label %} {% endif %} {% endfor %}
@@ -38,7 +38,7 @@

Labeled Objects in {{assessment}