Skip to content

Commit 8a1240f

Browse files
committed
Updated targetlist page to use HTMX tables
1 parent 9ade7f4 commit 8a1240f

4 files changed

Lines changed: 107 additions & 48 deletions

File tree

mop/settings.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
'django_filters',
5353
'django_gravatar',
5454
'django_htmx',
55+
'django_tables2',
5556
'rest_framework',
5657
'rest_framework.authtoken',
5758
'tom_targets',
@@ -435,6 +436,15 @@
435436
# {'name': 'TNS_class', 'type': 'string', 'default': 'None'}
436437
]
437438

439+
# Map model labels to dotted paths of custom general search functions for HTMX tables.
440+
# This allows overriding the default general search method without subclassing.
441+
# See https://tom-toolkit.readthedocs.io/en/stable/customization/htmx_tables.html.
442+
# Example:
443+
# GENERAL_SEARCH_FUNCTIONS = {
444+
# 'tom_targets.Target': 'custom_code.search.my_target_search',
445+
# }
446+
GENERAL_SEARCH_FUNCTIONS = {}
447+
438448
SELECTION_EXTRA_FIELDS = [
439449
'mag_now',
440450
'tap_priority',
@@ -467,6 +477,13 @@
467477
# or READ_ONLY (read only access to views)
468478
AUTH_STRATEGY = 'READ_ONLY'
469479

480+
# Display these columns in the target list table. Values can be attributes or properties on
481+
# the Target model, tags or extra fields. The fields `observations` and `saved_data` are
482+
# special cases with custom implementation.
483+
TARGET_LIST_COLUMNS = [
484+
"name", "type", "observations", "saved_data"
485+
]
486+
470487
# URLs that should be allowed access even with AUTH_STRATEGY = LOCKED
471488
# for example: OPEN_URLS = ['/', '/about']
472489
OPEN_URLS = ['/mop/Home']
@@ -494,6 +511,10 @@
494511
# 'plotly', 'plotly_white', 'plotly_dark', 'ggplot2', 'seaborn', 'simple_white', 'none'
495512
PLOTLY_THEME = 'plotly_white'
496513

514+
# Setting for displaying pagination information (e.g., "(0-0 of 0)").
515+
# Set this to False if you have a particularly large DB and paginated views are slow.
516+
SHOW_PAGINATION_INFO = True
517+
497518
try:
498519
from local_settings import * # noqa
499520
except ImportError:

poetry.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ fonttools = ">4.60.2"
3535
jinja2 = ">=3.1.6"
3636
requests = ">=2.32.4"
3737
markdown = ">=3.8.1"
38-
38+
django-tables2 = ">=2.6.0"
3939

4040

4141
[tool.poetry.group.dev.dependencies]
Lines changed: 84 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
{% extends 'tom_common/base.html' %}
2-
{% load bootstrap4 targets_extras dataproduct_extras %}
2+
{% load bootstrap4 targets_extras dataproduct_extras dataservices_extras %}
3+
{% load render_table from django_tables2 %}
4+
{% load i18n %}
5+
{% load crispy_forms_tags %}
6+
37
{% block title %}Targets{% endblock %}
8+
49
{% block content %}
510
<div class="row content">
6-
<div class="col-md-10">
11+
<div class="col-md-12">
712
<div class="row">
813
<div class="col-md-12">
914
<span class="float-right">
10-
{{ target_count }} Targets &nbsp;
15+
{{ target_count }} Target{{ target_count|pluralize }} &nbsp;
1116
<button class="btn btn-primary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
1217
Create Targets
1318
</button>
@@ -16,59 +21,92 @@
1621
<a class="dropdown-item" href="{% url 'targets:import' %}" title="Import Targets">Import Targets</a>
1722
<a class="dropdown-item" href="{% url 'tom_catalogs:query' %}" title="Catalog Search">Catalog Search</a>
1823
</div>
24+
{% catalog_query_menu %}
25+
1926
{% update_broker_data_button %}
20-
<button onclick="document.getElementById('invisible-export-button').click()" class="btn btn-primary">Export Filtered Targets</button>
21-
<!-- use an invisible button, because the key "Enter" event will triggered the first submit button and we want the default action to be applying filter -->
27+
28+
<!-- Export Filtered Targets button (gets current filter form values for export list)-->
29+
<button type="button" class="btn btn-primary" title="Export Filtered Targets"
30+
onclick="const queryString = new URLSearchParams(new FormData(document.querySelector('form.mb-3'))).toString(); window.location.href = '{% url 'targets:export' %}?' + queryString;">
31+
Export Filtered Targets
32+
</button>
2233
</span>
2334
</div>
2435
</div>
2536
<div class="row">
26-
<div class="col-md-6">
27-
{% aladin_skymap object_list %}
37+
<div class="col-md-6">
38+
{# Aladin skymap -- initialized once on page load (Moon/Sun computed here). #}
39+
{# On HTMX filter updates, only the target markers are updated via OOB swap #}
40+
{# into the aladin-targets-data div below (see target_table_partial.html). #}
41+
{% aladin_skymap skymap_objects %}
42+
43+
{# Placeholder for OOB target data updates from HTMX #}
44+
<div id="aladin-targets-data"></div>
2845
</div>
46+
2947
<div class="col-md-6">
30-
<label id="displaySelected"></label>
31-
<button id="optionSelectAll" type="button" class="btn btn-link" onClick="select_all({{ target_count }})"></button>
32-
<form id="grouping-form" action="{% url 'targets:add-remove-grouping' %}" method="POST">
33-
{% csrf_token %}
34-
<div class="d-flex flex-row align-items-baseline">
48+
49+
<form id="grouping-form" action="{% url 'targets:add-remove-grouping' %}" method="POST">
50+
{% csrf_token %}
51+
<div class="d-flex flex-row align-items-baseline">
3552
<label>Merge duplicate targets</label>
3653
<button type="submit" class="btn btn-outline-success ml-1" name="merge">Merge</button>
37-
</div>
38-
<div class="form-group d-flex justify-content-end align-items-baseline">
39-
<label>Add/Remove from grouping</label>
40-
<select name="grouping" class="form-control w-25 ml-1">
41-
{% for grouping in groupings %}
42-
<option value="{{ grouping.id }}">{{ grouping.name }}</option>
43-
{% endfor %}
44-
</select>
45-
<input type="hidden" value="{{ query_string }}" name="query_string">
46-
<input type="hidden" value="False" id="isSelectAll" name="isSelectAll">
47-
<button type="submit" class="btn btn-outline-primary ml-1" name="add">Add</button>
48-
<button type="submit" class="btn btn-outline-primary ml-1" name="move">Move</button>
49-
<button type="submit" class="btn btn-outline-danger ml-1" name="remove">Remove</button>
50-
</div>
51-
{% bootstrap_pagination page_obj extra=request.GET.urlencode %}
52-
{% target_table object_list %}
53-
{% bootstrap_pagination page_obj extra=request.GET.urlencode %}
54-
</form>
5554
</div>
56-
</div>
57-
</div>
58-
{% select_target_js %}
55+
<div class="form-group d-flex flex-row align-items-baseline">
56+
<label>Add/Remove from grouping</label>
57+
<select name="grouping" class="form-control w-25 ml-1">
58+
{% for grouping in groupings %}
59+
<option value="{{ grouping.id }}">{{ grouping.name }}</option>
60+
{% endfor %}
61+
</select>
62+
<input type="hidden" value="{{ query_string }}" name="query_string">
63+
<input type="hidden" value="False" id="isSelectAll" name="isSelectAll">
64+
<button type="submit" class="btn btn-outline-primary ml-1" name="add">Add</button>
65+
<button type="submit" class="btn btn-outline-primary ml-1" name="move">Move</button>
66+
<button type="submit" class="btn btn-outline-danger ml-1" name="remove">Remove</button>
67+
</div>
68+
</form>
5969

60-
{{ filter.fields }}
61-
<div class="col-md-2">
62-
<form action="" method="get" class="form">
63-
{% bootstrap_form filter.form %}
64-
{% buttons %}
65-
<button type="submit" class="btn btn-primary">
66-
Filter
67-
</button>
68-
<a href="{% url 'targets:list' %}" class="btn btn-secondary" title="Reset">Reset</a>
69-
<button type="submit" formaction="{% url 'targets:export' %}" id="invisible-export-button" style="display:none"></button>
70-
{% endbuttons %}
70+
{% block target_table_content %}
71+
<hr>
72+
{# Search form (we refer to this id="filter-form" in the TargetTable.Meta.attrs) #}
73+
<form id="filter-form" class="mb-3"
74+
hx-get="{% url 'tom_targets:list' %}"
75+
hx-target="div.table-container"
76+
hx-swap="outerHTML"
77+
hx-indicator=".progress">
78+
{% crispy filter.form %}
7179
</form>
80+
81+
{# Progress indicator #}
82+
<div class="progress">
83+
<div class="indeterminate"></div>
84+
</div>
85+
86+
{# The actual table #}
87+
<div class="table-container">
88+
{% include "tom_targets/partials/target_table_partial.html" %}
89+
</div>
90+
91+
<hr>
92+
{% endblock target_table_content %}
7293
</div>
73-
</div>
74-
{% endblock %}
94+
</div>
95+
</div>
96+
97+
<script>
98+
document.body.addEventListener('change', function(e) {
99+
// This eventListener toggles all the checkboxes when the header-checkbox is toggled.
100+
101+
// Check if the source of the change event is our header checkbox
102+
if (e.target && e.target.classList.contains('header-checkbox')) {
103+
104+
// Find all checkboxes with the specific name used in the rows
105+
const checkboxes = document.querySelectorAll('input[name="selected-target"]');
106+
107+
// Set their state to match the header checkbox
108+
checkboxes.forEach(cb => cb.checked = e.target.checked);
109+
}
110+
});
111+
</script>
112+
{% endblock content %}

0 commit comments

Comments
 (0)