new _applets partial to govern applet list; home.html updated accordingly to incl partial; fixed seed migrations for palette convention from last commit; new text_view ITs & views to govern applet visibility/toggling
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Disco DeDisco
2026-03-05 16:08:40 -05:00
parent c099479740
commit 20c5f6f589
6 changed files with 132 additions and 36 deletions

View File

@@ -4,7 +4,7 @@ from django.db import migrations
def seed_applets(apps, schema_editor):
Applet = apps.get_model("dashboard", "Applet")
Applet.objects.get_or_create(slug="username", defaults={"name": "Username"})
Applet.objects.get_or_create(slug="theme-switcher", defaults={"name": "Theme Switcher"})
Applet.objects.get_or_create(slug="palette", defaults={"name": "Palette"})
class Migration(migrations.Migration):

View File

@@ -210,7 +210,11 @@ class ShareListTest(TestCase):
f"/dashboard/list/{our_list.id}/share_list",
data={"recipient": "nobody@example.com"},
)
self.assertRedirects(response, f"/dashboard/list/{our_list.id}/")
self.assertRedirects(
response,
f"/dashboard/list/{our_list.id}/",
fetch_redirect_response=False,
)
def test_share_list_does_not_add_owner_as_recipient(self):
owner = User.objects.create(email="owner@example.com")
@@ -374,3 +378,27 @@ class ToggleAppletsViewTest(TestCase):
HTTP_HX_REQUEST="true",
)
self.assertEqual(response.status_code, 200)
def test_htmx_post_renders_visible_applets_only(self):
response = self.client.post(
self.url,
{"applets": ["username"]},
HTTP_HX_REQUEST="true",
)
parsed = lxml.html.fromstring(response.content)
self.assertEqual(len(parsed.cssselect("#id_applet_username")), 1)
self.assertEqual(len(parsed.cssselect("#id_applet_palette")), 0)
class AppletVisibilityContextTest(TestCase):
def setUp(self):
self.user = User.objects.create(email="disco@test.io")
self.client.force_login(self.user)
self.username_applet, _ = Applet.objects.get_or_create(slug="username", defaults={"name": "Username"})
self.palette_applet, _ = Applet.objects.get_or_create(slug="palette", defaults={"name": "Palette"})
UserApplet.objects.create(user=self.user, applet=self.palette_applet, visible=False)
def test_dash_reflects_user_applet_visibility(self):
response = self.client.get("/")
applet_map = {entry["applet"].slug: entry["visible"] for entry in response.context["applets"]}
self.assertFalse(applet_map["palette"])
self.assertTrue(applet_map["username"])

View File

@@ -16,12 +16,18 @@ PALETTES = [
]
def _applet_context(user):
ua_map = {ua.applet_id: ua.visible for ua in user.user_applets.all()}
return [
{"applet": applet, "visible": ua_map.get(applet.pk, applet.default_visible)}
for applet in Applet.objects.all()
]
def home_page(request):
return render(
request, "apps/dashboard/home.html", {
"form": ItemForm(),
"palettes": PALETTES,
})
context = {"form": ItemForm(), "palettes": PALETTES}
if request.user.is_authenticated:
context["applets"] = _applet_context(request.user)
return render(request, "apps/dashboard/home.html", context)
def new_list(request):
form = ItemForm(data=request.POST)
@@ -100,5 +106,8 @@ def toggle_applets(request):
defaults={"visible": applet.slug in checked},
)
if request.headers.get("HX-Request"):
return HttpResponse("")
return render(request, "apps/dashboard/_partials/_applets.html", {
"applets": _applet_context(request.user),
"palettes": PALETTES,
})
return redirect("home")

View File

@@ -3,9 +3,15 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from .base import FunctionalTest
from apps.dashboard.models import Applet
class DashboardMaintenanceTest(FunctionalTest):
def setUp(self):
super().setUp()
Applet.objects.get_or_create(slug="username", defaults={"name": "Username"})
Applet.objects.get_or_create(slug="palette", defaults={"name": "Palette"})
def test_user_without_username_can_claim_unclaimed_username(self):
# 1. Create a pre-authenticated session for discoman@example.com
self.create_pre_authenticated_session("discoman@example.com")
@@ -51,18 +57,20 @@ class DashboardMaintenanceTest(FunctionalTest):
dash_gear.click()
# 4. A menu appears; wait_for el w. id="id_applet_menu"
self.wait_for(
lambda: self.browser.find_element(By.ID, "id_applet_menu")
lambda: self.assertTrue(
self.browser.find_element(By.ID, "id_applet_menu").is_displayed()
)
)
# 5. Find two checkboxes in menu, name="username" & name="palette"; assert both .is_selected()
menu = self.browser.find_element(By.ID, "id_applet_menu")
username_cb = menu.find_element(By.NAME, "username")
palette_cb = menu.find_element(By.NAME, "palette")
username_cb = menu.find_element(By.CSS_SELECTOR, '[name="applets"][value="username"]')
palette_cb = menu.find_element(By.CSS_SELECTOR, '[name="applets"][value="palette"]')
self.assertTrue(username_cb.is_selected())
self.assertTrue(palette_cb.is_selected())
# 6. Click palette box to uncheck it
palette_cb.click()
self.assertFalse(palette_cb.is_selected())
self.browser.execute_script("window.__no_reload_marker = True")
self.browser.execute_script("window.__no_reload_marker = true")
# 7. Submit the menu form via [type="submit"] btn inside menu
menu.find_element(By.CSS_SELECTOR, '[type="submit"]').click()
self.wait_for(
@@ -82,6 +90,13 @@ class DashboardMaintenanceTest(FunctionalTest):
self.browser.find_element(By.ID, "id_applet_username")
# 10. Click gear again, find menu, find palette checkbox; assert now NOT selected
dash_gear.click()
self.wait_for(
lambda: self.assertTrue(
self.browser.find_element(By.ID, "id_applet_menu").is_displayed()
)
)
menu = self.browser.find_element(By.ID, "id_applet_menu")
palette_cb = menu.find_element(By.CSS_SELECTOR, '[name="applets"][value="palette"]')
self.assertFalse(palette_cb.is_selected())
# 11. Click it to re-check box; submit
palette_cb.click()

View File

@@ -0,0 +1,61 @@
{% load lyric_extras %}
<div id="id_applets_container">
<div id="id_applet_menu" style="display:none;">
<form
hx-post="{% url "toggle_applets" %}"
hx-target="#id_applets_container"
hx-swap="outerHTML"
>
{% csrf_token %}
{% for entry in applets %}
<label>
<input
type="checkbox"
name="applets"
value="{{ entry.applet.slug }}"
{% if entry.visible %}checked{% endif %}
>
{{ entry.applet.name }}
</label>
{% endfor %}
<button type="submit" class="btn btn-confirm">OK</button>
</form>
</div>
{% for entry in applets %}
{% if entry.visible %}
{% if entry.applet.slug == "username" %}
<section id="id_applet_username">
<h1>{{ user|display_name }}</h1>
<div class="form-container">
<form method="POST" action="{% url "set_profile" %}">
{% csrf_token %}
<input
id="id_new_username"
name="username"
required
value="{{ user.username|default:'' }}"
>
</form>
</div>
</section>
{% elif entry.applet.slug == "palette" %}
<section id="id_applet_palette" class="palette">
{% for palette in palettes %}
<div class="palette-item">
<div class="swatch {{ palette.name }}{% if user_palette == palette.name %} active{% endif %}{% if palette.locked %} locked{% endif %}"></div>
{% if not palette.locked %}
<form method="POST" action="{% url "set_palette" %}">
{% csrf_token %}
<button type="submit" name="palette" value="{{ palette.name }}" class="btn btn-confirm">OK</button>
</form>
{% else %}
<span class="btn btn-disabled">&times;</span>
{% endif %}
</div>
{% endfor %}
</section>
{% endif %}
{% endif %}
{% endfor %}
</div>

View File

@@ -15,29 +15,12 @@
{% block content %}
{% if user.is_authenticated %}
<section id="id_applet_palette" class="palette">
{% for palette in palettes %}
<div class="palette-item">
<div class="swatch {{ palette.name }}{% if user_palette == palette.name %} active{% endif %}{% if palette.locked %} locked{% endif %}"></div>
{% if not palette.locked %}
<form method="POST" action="{% url 'set_palette' %}">
{% csrf_token %}
<button type="submit" name="palette" value="{{ palette.name }}" class="btn btn-confirm">OK</button>
</form>
{% else %}
<span class="btn btn-disabled">&times;</span>
{% endif %}
</div>
{% endfor %}
</section>
<section id="id_applet_username">
<h1>{{ user|display_name }}</h1>
<div class="form-container">
<form method="POST" action="{% url "set_profile" %}">
{% csrf_token %}
<input id="id_new_username" name="username" required value="{{ user.username|default:'' }}">
</form>
</div>
</section>
<button
id="id_dash_gear"
onclick="document.getElementById('id_applet_menu').style.display='block'"
>
&#9881;
</button>
{% include "apps/dashboard/_partials/_applets.html" %}
{% endif %}
{% endblock content %}