Django-filters и HTMX: Запуск фильтра на нескольких представлениях одновременно
У меня есть простое приложение, в котором я хочу, чтобы один фильтр динамически обновлял набор запросов в двух разных представлениях одновременно.
В этом примере пользователь может фильтровать на основе города/страны/континента, и я хотел бы динамически обновлять (1) таблицу, показывающую соответствующие объекты в модели, и (2) листовую карту для нанесения точек.
Я думаю, что основная проблема здесь в том, что мне нужно инициировать обновление набора фильтров для нескольких "представлений" одновременно, и я не уверен, как структурировать мой проект, чтобы достичь этого. Или если это правильный способ думать об этой проблеме.
Я пытаюсь заставить фильтр работать с{% for city in cityFilterResults %}
итератором в двух разных представлениях одновременно.
Как я могу добиться обновления двух разных представлений на основе фильтра с помощью HTMX?
index.html
:
(Примечание: ожидаемое поведение работает, если я переключаю URL hx-get
между таблицей и картой. Но они не фильтруются вместе, и именно на этом я застрял.)
<body>
<h3>Filter Controls:</h3><br>
<form hx-get="{% url '_city_table' %}" hx-target="#cityTable">
<!-- <form hx-get="{% url '_leaflet' %}" hx-target="#markers"> -->
{% csrf_token %}
{{ cityFilterForm.form.as_p }}
<button type="submit" class="btn btn-primary"> Filter </button>
</form>
[...]
<!-- Table Body -->
<tbody id="cityTable" hx-get="{% url '_city_table' %}" hx-trigger="load" hx-target="nearest tr"> </tbody>
[...]
<!-- Div to get the markers -->
<div id="markers" hx-get="{% url '_leaflet' %}" hx-trigger="my_custom_trigger from:body"> </div>
</body>
<script>
[... Leaflet stuff ...]
document.getElementById('markers');
</script>
_city_table.html
:
{% for city in cityFilterResults %}
<tr>
<td> {{ city.city }} </td>
<td> {{ city.country }} </td>
<td> {{ city.continent }} </td>
<td> {{ city.latitude|floatformat:4 }} </td>
<td> {{ city.longitude|floatformat:4 }} </td>
</tr>
{% endfor %}
_leaflet.html
:
<script>
if (map.hasLayer(group)) {
console.log('LayerGroup already exists');
group.clearLayers();
map.removeLayer(group);
} else {
console.log('LayerGroup doesn't exist');
}
{% for city in cityFilterResults %}
var marker = L.marker([{{city.latitude}}, {{city.longitude}}]).addTo(group)
.bindPopup("{{city.city}}")
{% endfor %}
group.addTo(map);
</script>
views.py
:
def _city_table(request):
city_filter_form = cityFilter(request.GET, queryset=cityModel.objects.all())
city_filter_results = city_filter_form.qs
context={ 'cityModel': cityModel.objects.all(),
'cityFilterResults': city_filter_results }
response = render(request, '_city_table.html', context)
response['HX-Trigger'] = 'my_custom_trigger'
return response
def _leaflet(request):
city_filter_form = cityFilter(request.GET, queryset=cityModel.objects.all())
city_filter_results = city_filter_form.qs
context={ 'cityModel': cityModel.objects.all(),
'cityFilterResults': city_filter_results }
return render(request, '_leaflet.html', context)
Когда hx-get
на форме Filter указывает на URL для шаблона _city_table
, то таблица фильтруется, как ожидалось, но карта - нет:
Когда hx-get
на форме Filter указывает на URL для шаблона _leaflet
, то карта фильтруется, а таблица - нет:
Делюсь решением своего вопроса. Оно работает, и это здорово, но мне бы очень хотелось понять, какие альтернативные подходы можно применить. Мой подход кажется мне хаком, и я думаю, что я что-то упускаю.
Я смог использовать session
для передачи фильтра между представлениями. В принципе, я использую форму Filter для таблицы. В "представлениях" для таблицы происходят две вещи: (1) я создаю session
и сохраняю результаты фильтра, и (2) возвращаю HTTP-ответ с заголовком, который служит триггером для HTMX в части Leaflet.
В шаблоне HTML у меня есть URL Leaflet, который ждет, пока пользовательский триггер HTMX вернется с таблицей. Затем в представлениях Leaflet я беру данные из session
.
views.py
:
def _city_table(request):
city_filter_form = cityFilter(request.GET, queryset=cityModel.objects.all())
city_filter_results = city_filter_form.qs
request.session['my_city_filter'] = [city.id for city in city_filter_results]
request.session.modified=True
context={ 'cityModel': cityModel.objects.all(),
'cityFilterResults': city_filter_results }
response = render(request, '_city_table.html', context)
response['HX-Trigger'] = 'my_custom_trigger'
return response
def _leaflet(request):
city_filter_results = [cityModel.objects.get(id=id) for id in request.session['my_city_filter']]
context={ 'cityModel': cityModel.objects.all(),
'cityFilterResults': city_filter_results }
return render(request, '_leaflet.html', context)
index.html
:
<div id="markers" hx-get="{% url '_leaflet' %}" hx-trigger="my_custom_trigger from:body"> </div>
Вы можете найти решение здесь: https://htmx.org/examples/update-other-content/
В частности, есть два решения
swap-oob ( https://htmx.org/examples/update-other-content/#oob )
Вы должны поместить вашу форму в действие вашего контроллера. Действие возвращает два элемента, которые будут заменены
<button hx-get="/Form/OutOfBandResponse" hx-swap="none">ok</button>
<div id="A"></div>
<div id="B"></div>
Вот ответ:
<!-- this will replace the element with id A -->
<div id="A" hx-swap-oob="true">
Joe Smith
</div>
<!-- this will replace the element with id B -->
<div id="B" hx-swap-oob="true">
Antony Queen
</div>
В качестве альтернативы можно использовать события. ( https://htmx.org/examples/update-other-content/#events )
Итак, нужно создать обработчик для события, затем при отправке формы нужно ответить заголовком, который вызывает это событие. Когда событие срабатывает, div'ы реагируют, обновляя себя.