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, то таблица фильтруется, как ожидалось, но карта - нет:

enter image description here

Когда hx-get на форме Filter указывает на URL для шаблона _leaflet, то карта фильтруется, а таблица - нет:

enter image description here

Делюсь решением своего вопроса. Оно работает, и это здорово, но мне бы очень хотелось понять, какие альтернативные подходы можно применить. Мой подход кажется мне хаком, и я думаю, что я что-то упускаю.


Я смог использовать 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'ы реагируют, обновляя себя.

Вернуться на верх