Tweaks to delete query flow

Animated demo: https://github.com/simonw/datasette/pull/2764#issuecomment-4655694668

Refs #2760
This commit is contained in:
Simon Willison 2026-06-08 20:18:16 -07:00
commit 1359de65c0
4 changed files with 54 additions and 29 deletions

View file

@ -32,6 +32,16 @@
display: flex;
gap: 1rem;
}
.query-delete-form input[type=submit] {
background: linear-gradient(180deg, #d73a31 0%, #b42318 100%);
border-color: #b42318;
font-weight: 700;
}
.query-delete-form input[type=submit]:hover,
.query-delete-form input[type=submit]:focus {
background: linear-gradient(180deg, #c3342b 0%, #971c14 100%);
border-color: #971c14;
}
</style>
{% endblock %}
@ -60,7 +70,7 @@
<dd><pre>{{ query.sql }}</pre></dd>
</dl>
<form action="{{ query_url }}/-/delete" method="post">
<form class="core query-delete-form" action="{{ query_url }}/-/delete" method="post">
<p class="query-delete-actions">
<input type="submit" value="Delete query">
<a href="{{ query_url }}">Cancel</a>

View file

@ -205,32 +205,32 @@
<h1 style="padding-left: 10px; border-left: 10px solid #{% if database_color %}{{ database_color }}{% else %}666{% endif %}">Queries</h1>
<form class="query-list-filters core" action="{{ query_list_path }}" method="get">
<p class="query-list-search">
<label for="query-search">Search</label>
<input id="query-search" type="search" name="q" value="{{ filters.q }}">
{% if filters.is_write %}<input type="hidden" name="is_write" value="{{ filters.is_write }}">{% endif %}
{% if filters.is_private %}<input type="hidden" name="is_private" value="{{ filters.is_private }}">{% endif %}
{% if filters.source %}<input type="hidden" name="source" value="{{ filters.source }}">{% endif %}
{% if filters.owner_id %}<input type="hidden" name="owner_id" value="{{ filters.owner_id }}">{% endif %}
<button type="submit">Search</button>
</p>
</form>
<nav class="query-list-facets" aria-label="Query filters">
{% for facet in facets %}
<section class="query-list-facet">
<h2>{{ facet.title }}</h2>
<ul>
{% for item in facet["items"] %}
<li>{% if item.href %}<a class="query-list-facet-link{% if item.active %} query-list-facet-link-active{% endif %}" href="{{ item.href }}"{% if item.active %} aria-current="true"{% endif %}>{% else %}<span class="query-list-facet-link query-list-facet-disabled">{% endif %}<span>{{ item.label }}</span><span class="query-list-facet-count">{{ item.count }}</span>{% if item.href %}</a>{% else %}</span>{% endif %}</li>
{% endfor %}
</ul>
</section>
{% endfor %}
</nav>
{% if queries %}
<form class="query-list-filters core" action="{{ query_list_path }}" method="get">
<p class="query-list-search">
<label for="query-search">Search</label>
<input id="query-search" type="search" name="q" value="{{ filters.q }}">
{% if filters.is_write %}<input type="hidden" name="is_write" value="{{ filters.is_write }}">{% endif %}
{% if filters.is_private %}<input type="hidden" name="is_private" value="{{ filters.is_private }}">{% endif %}
{% if filters.source %}<input type="hidden" name="source" value="{{ filters.source }}">{% endif %}
{% if filters.owner_id %}<input type="hidden" name="owner_id" value="{{ filters.owner_id }}">{% endif %}
<button type="submit">Search</button>
</p>
</form>
<nav class="query-list-facets" aria-label="Query filters">
{% for facet in facets %}
<section class="query-list-facet">
<h2>{{ facet.title }}</h2>
<ul>
{% for item in facet["items"] %}
<li>{% if item.href %}<a class="query-list-facet-link{% if item.active %} query-list-facet-link-active{% endif %}" href="{{ item.href }}"{% if item.active %} aria-current="true"{% endif %}>{% else %}<span class="query-list-facet-link query-list-facet-disabled">{% endif %}<span>{{ item.label }}</span><span class="query-list-facet-count">{{ item.count }}</span>{% if item.href %}</a>{% else %}</span>{% endif %}</li>
{% endfor %}
</ul>
</section>
{% endfor %}
</nav>
<div class="table-wrapper"><table class="query-list-results">
<thead>
<tr>

View file

@ -641,6 +641,4 @@ class QueryDeleteView(BaseView):
"Query “{}” deleted".format(existing.title or query_name),
self.ds.INFO,
)
return Response.redirect(
self.ds.urls.path(self.ds.urls.database(db.name) + "/-/queries")
)
return Response.redirect(self.ds.urls.path(self.ds.urls.database(db.name)))

View file

@ -3,6 +3,7 @@ import re
from html import unescape
import pytest
from bs4 import BeautifulSoup as Soup
from datasette.app import Datasette
from datasette.resources import DatabaseResource, QueryResource
@ -712,6 +713,10 @@ async def test_query_list_search_filter_and_html():
"/data/-/queries?is_private=1",
actor={"id": "root"},
)
no_results_response = await ds.client.get(
"/data/-/queries?q=nope",
actor={"id": "root"},
)
assert html_response.status_code == 200
assert "Demo query 02" in html_response.text
@ -799,6 +804,13 @@ async def test_query_list_search_filter_and_html():
'<span class="query-list-facet-link query-list-facet-disabled"><span>Not private</span><span class="query-list-facet-count">0</span></span>'
not in filtered_private_response.text
)
assert no_results_response.status_code == 200
assert "No queries found." in no_results_response.text
assert 'class="query-list-filters core"' not in no_results_response.text
assert 'id="query-search"' not in no_results_response.text
assert 'class="query-list-facets"' not in no_results_response.text
assert "<h2>Mode</h2>" not in no_results_response.text
assert "<h2>Visibility</h2>" not in no_results_response.text
@pytest.mark.asyncio
@ -1274,6 +1286,11 @@ async def test_query_delete_confirmation_and_form_delete():
assert get_response.status_code == 200
assert "Are you sure" in get_response.text
assert "select * from dogs" in get_response.text
soup = Soup(get_response.text, "html.parser")
form = soup.select_one("form.query-delete-form")
assert form is not None
assert "core" in form["class"]
assert form.select_one('input[type="submit"][value="Delete query"]') is not None
post_response = await ds.client.post(
"/data/saved/-/delete",
@ -1281,7 +1298,7 @@ async def test_query_delete_confirmation_and_form_delete():
data={},
)
assert post_response.status_code == 302
assert post_response.headers["location"] == "/data/-/queries"
assert post_response.headers["location"] == "/data"
assert await ds.get_query("data", "saved") is None