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
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -4,7 +4,7 @@ from django.db import migrations
|
|||||||
def seed_applets(apps, schema_editor):
|
def seed_applets(apps, schema_editor):
|
||||||
Applet = apps.get_model("dashboard", "Applet")
|
Applet = apps.get_model("dashboard", "Applet")
|
||||||
Applet.objects.get_or_create(slug="username", defaults={"name": "Username"})
|
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):
|
class Migration(migrations.Migration):
|
||||||
|
|||||||
@@ -210,7 +210,11 @@ class ShareListTest(TestCase):
|
|||||||
f"/dashboard/list/{our_list.id}/share_list",
|
f"/dashboard/list/{our_list.id}/share_list",
|
||||||
data={"recipient": "nobody@example.com"},
|
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):
|
def test_share_list_does_not_add_owner_as_recipient(self):
|
||||||
owner = User.objects.create(email="owner@example.com")
|
owner = User.objects.create(email="owner@example.com")
|
||||||
@@ -374,3 +378,27 @@ class ToggleAppletsViewTest(TestCase):
|
|||||||
HTTP_HX_REQUEST="true",
|
HTTP_HX_REQUEST="true",
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
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"])
|
||||||
|
|||||||
@@ -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):
|
def home_page(request):
|
||||||
return render(
|
context = {"form": ItemForm(), "palettes": PALETTES}
|
||||||
request, "apps/dashboard/home.html", {
|
if request.user.is_authenticated:
|
||||||
"form": ItemForm(),
|
context["applets"] = _applet_context(request.user)
|
||||||
"palettes": PALETTES,
|
return render(request, "apps/dashboard/home.html", context)
|
||||||
})
|
|
||||||
|
|
||||||
def new_list(request):
|
def new_list(request):
|
||||||
form = ItemForm(data=request.POST)
|
form = ItemForm(data=request.POST)
|
||||||
@@ -100,5 +106,8 @@ def toggle_applets(request):
|
|||||||
defaults={"visible": applet.slug in checked},
|
defaults={"visible": applet.slug in checked},
|
||||||
)
|
)
|
||||||
if request.headers.get("HX-Request"):
|
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")
|
return redirect("home")
|
||||||
|
|||||||
@@ -3,9 +3,15 @@ from selenium.webdriver.common.by import By
|
|||||||
from selenium.webdriver.common.keys import Keys
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
|
||||||
from .base import FunctionalTest
|
from .base import FunctionalTest
|
||||||
|
from apps.dashboard.models import Applet
|
||||||
|
|
||||||
|
|
||||||
class DashboardMaintenanceTest(FunctionalTest):
|
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):
|
def test_user_without_username_can_claim_unclaimed_username(self):
|
||||||
# 1. Create a pre-authenticated session for discoman@example.com
|
# 1. Create a pre-authenticated session for discoman@example.com
|
||||||
self.create_pre_authenticated_session("discoman@example.com")
|
self.create_pre_authenticated_session("discoman@example.com")
|
||||||
@@ -51,18 +57,20 @@ class DashboardMaintenanceTest(FunctionalTest):
|
|||||||
dash_gear.click()
|
dash_gear.click()
|
||||||
# 4. A menu appears; wait_for el w. id="id_applet_menu"
|
# 4. A menu appears; wait_for el w. id="id_applet_menu"
|
||||||
self.wait_for(
|
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()
|
# 5. Find two checkboxes in menu, name="username" & name="palette"; assert both .is_selected()
|
||||||
menu = self.browser.find_element(By.ID, "id_applet_menu")
|
menu = self.browser.find_element(By.ID, "id_applet_menu")
|
||||||
username_cb = menu.find_element(By.NAME, "username")
|
username_cb = menu.find_element(By.CSS_SELECTOR, '[name="applets"][value="username"]')
|
||||||
palette_cb = menu.find_element(By.NAME, "palette")
|
palette_cb = menu.find_element(By.CSS_SELECTOR, '[name="applets"][value="palette"]')
|
||||||
self.assertTrue(username_cb.is_selected())
|
self.assertTrue(username_cb.is_selected())
|
||||||
self.assertTrue(palette_cb.is_selected())
|
self.assertTrue(palette_cb.is_selected())
|
||||||
# 6. Click palette box to uncheck it
|
# 6. Click palette box to uncheck it
|
||||||
palette_cb.click()
|
palette_cb.click()
|
||||||
self.assertFalse(palette_cb.is_selected())
|
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
|
# 7. Submit the menu form via [type="submit"] btn inside menu
|
||||||
menu.find_element(By.CSS_SELECTOR, '[type="submit"]').click()
|
menu.find_element(By.CSS_SELECTOR, '[type="submit"]').click()
|
||||||
self.wait_for(
|
self.wait_for(
|
||||||
@@ -82,6 +90,13 @@ class DashboardMaintenanceTest(FunctionalTest):
|
|||||||
self.browser.find_element(By.ID, "id_applet_username")
|
self.browser.find_element(By.ID, "id_applet_username")
|
||||||
# 10. Click gear again, find menu, find palette checkbox; assert now NOT selected
|
# 10. Click gear again, find menu, find palette checkbox; assert now NOT selected
|
||||||
dash_gear.click()
|
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())
|
self.assertFalse(palette_cb.is_selected())
|
||||||
# 11. Click it to re-check box; submit
|
# 11. Click it to re-check box; submit
|
||||||
palette_cb.click()
|
palette_cb.click()
|
||||||
|
|||||||
61
src/templates/apps/dashboard/_partials/_applets.html
Normal file
61
src/templates/apps/dashboard/_partials/_applets.html
Normal 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">×</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
@@ -15,29 +15,12 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<section id="id_applet_palette" class="palette">
|
<button
|
||||||
{% for palette in palettes %}
|
id="id_dash_gear"
|
||||||
<div class="palette-item">
|
onclick="document.getElementById('id_applet_menu').style.display='block'"
|
||||||
<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' %}">
|
</button>
|
||||||
{% csrf_token %}
|
{% include "apps/dashboard/_partials/_applets.html" %}
|
||||||
<button type="submit" name="palette" value="{{ palette.name }}" class="btn btn-confirm">OK</button>
|
|
||||||
</form>
|
|
||||||
{% else %}
|
|
||||||
<span class="btn btn-disabled">×</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>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
Reference in New Issue
Block a user