'theme_switcher,' 'theme-picker' & 'theme' renamed everywhere to simply 'palette'; new urls & views & their corresponding ITs ensure applet menu checkbox functionality
This commit is contained in:
@@ -82,7 +82,7 @@ class AppletModelTest(TestCase):
|
||||
class UserAppletModelTest(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create(email="a@b.cde")
|
||||
self.applet = Applet.objects.create(slug="username", name="Username")
|
||||
self.applet, _ = Applet.objects.get_or_create(slug="username", defaults={"name": "Username"})
|
||||
|
||||
def test_user_applet_links_user_to_applet(self):
|
||||
ua = UserApplet.objects.create(user=self.user, applet=self.applet, visible=True)
|
||||
|
||||
@@ -9,7 +9,7 @@ from apps.dashboard.forms import (
|
||||
DUPLICATE_ITEM_ERROR,
|
||||
EMPTY_ITEM_ERROR,
|
||||
)
|
||||
from apps.dashboard.models import Item, List
|
||||
from apps.dashboard.models import Applet, Item, List, UserApplet
|
||||
from apps.lyric.models import User
|
||||
|
||||
|
||||
@@ -256,45 +256,45 @@ class ViewAuthListTest(TestCase):
|
||||
response = self.client.get(reverse("view_list", args=[self.our_list.id]))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
class SetThemeTest(TestCase):
|
||||
class SetPaletteTest(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create(email="a@b.cde")
|
||||
self.client.force_login(self.user)
|
||||
self.url = reverse("home")
|
||||
|
||||
def test_anonymous_user_is_redirected_home(self):
|
||||
response = self.client.post("/dashboard/set_theme")
|
||||
response = self.client.post("/dashboard/set_palette")
|
||||
self.assertRedirects(response, "/")
|
||||
|
||||
def test_set_theme_updates_user_theme(self):
|
||||
User.objects.filter(pk=self.user.pk).update(theme="theme-sheol")
|
||||
self.client.post("/dashboard/set_theme", data={"theme": "theme-default"})
|
||||
def test_set_palette_updates_user_palette(self):
|
||||
User.objects.filter(pk=self.user.pk).update(palette="palette-sheol")
|
||||
self.client.post("/dashboard/set_palette", data={"palette": "palette-default"})
|
||||
self.user.refresh_from_db()
|
||||
self.assertEqual(self.user.theme, "theme-default")
|
||||
self.assertEqual(self.user.palette, "palette-default")
|
||||
|
||||
def test_locked_theme_is_rejected(self):
|
||||
response = self.client.post("/dashboard/set_theme", data={"theme": "theme-nirvana"})
|
||||
def test_locked_palette_is_rejected(self):
|
||||
response = self.client.post("/dashboard/set_palette", data={"palette": "palette-nirvana"})
|
||||
self.user.refresh_from_db()
|
||||
self.assertEqual(self.user.theme, "theme-default")
|
||||
self.assertEqual(self.user.palette, "palette-default")
|
||||
self.assertRedirects(response, "/")
|
||||
|
||||
def test_set_theme_redirects_home(self):
|
||||
response = self.client.post("/dashboard/set_theme", data={"theme": "theme-default"})
|
||||
def test_set_palette_redirects_home(self):
|
||||
response = self.client.post("/dashboard/set_palette", data={"palette": "palette-default"})
|
||||
self.assertRedirects(response, "/")
|
||||
|
||||
def test_my_lists_contains_set_theme_form(self):
|
||||
def test_my_lists_contains_set_palette_form(self):
|
||||
response = self.client.get(self.url)
|
||||
parsed = lxml.html.fromstring(response.content)
|
||||
forms = parsed.cssselect('form[action="/dashboard/set_theme"]')
|
||||
forms = parsed.cssselect('form[action="/dashboard/set_palette"]')
|
||||
self.assertEqual(len(forms), 1)
|
||||
|
||||
def test_active_theme_swatch_has_active_class(self):
|
||||
def test_active_palette_swatch_has_active_class(self):
|
||||
response = self.client.get(self.url)
|
||||
parsed = lxml.html.fromstring(response.content)
|
||||
[active] = parsed.cssselect(".swatch.active")
|
||||
self.assertIn("theme-default", active.classes)
|
||||
self.assertIn("palette-default", active.classes)
|
||||
|
||||
def test_locked_themes_are_not_forms(self):
|
||||
def test_locked_palettes_are_not_forms(self):
|
||||
response = self.client.get(self.url)
|
||||
parsed = lxml.html.fromstring(response.content)
|
||||
locked = parsed.cssselect(".swatch.locked")
|
||||
@@ -303,11 +303,11 @@ class SetThemeTest(TestCase):
|
||||
for swatch in locked:
|
||||
self.assertNotEqual(swatch.tag, "button")
|
||||
|
||||
def test_theme_picker_count_matches_context(self):
|
||||
def test_palette_picker_count_matches_context(self):
|
||||
response = self.client.get(self.url)
|
||||
parsed = lxml.html.fromstring(response.content)
|
||||
swatches = parsed.cssselect(".swatch")
|
||||
self.assertEqual(len(swatches), len(response.context["themes"]))
|
||||
self.assertEqual(len(swatches), len(response.context["palettes"]))
|
||||
|
||||
class ProfileViewTest(TestCase):
|
||||
def setUp(self):
|
||||
@@ -341,3 +341,36 @@ class ProfileViewTest(TestCase):
|
||||
[username_input] = parsed.cssselect("#id_new_username")
|
||||
self.assertEqual("discoman", username_input.get("value"))
|
||||
|
||||
class ToggleAppletsViewTest(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"})
|
||||
self.url = reverse("toggle_applets")
|
||||
|
||||
def test_unauthenticated_user_is_redirected(self):
|
||||
self.client.logout()
|
||||
response = self.client.post(self.url)
|
||||
self.assertRedirects(
|
||||
response, f"/?next={self.url}", fetch_redirect_response=False
|
||||
)
|
||||
|
||||
def test_unchecked_applet_gets_user_applet_with_visible_false(self):
|
||||
self.client.post(self.url, {"applets": ["username"]})
|
||||
ua = UserApplet.objects.get(user=self.user, applet=self.palette_applet)
|
||||
self.assertFalse(ua.visible)
|
||||
|
||||
def test_redirects_on_normal_post(self):
|
||||
response = self.client.post(
|
||||
self.url, {"applets": ["username", "palette"]}
|
||||
)
|
||||
self.assertRedirects(response, reverse("home"), fetch_redirect_response=False)
|
||||
|
||||
def test_returns_200_on_htmx_post(self):
|
||||
response = self.client.post(
|
||||
self.url,
|
||||
{"applets": ["username", "palette"]},
|
||||
HTTP_HX_REQUEST="true",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@@ -5,7 +5,8 @@ urlpatterns = [
|
||||
path('new_list', views.new_list, name='new_list'),
|
||||
path('list/<uuid:list_id>/', views.view_list, name='view_list'),
|
||||
path('list/<uuid:list_id>/share_list', views.share_list, name="share_list"),
|
||||
path('set_theme', views.set_theme, name='set_theme'),
|
||||
path('set_palette', views.set_palette, name='set_palette'),
|
||||
path('set_profile', views.set_profile, name='set_profile'),
|
||||
path('users/<uuid:user_id>/', views.my_lists, name='my_lists'),
|
||||
path('toggle_applets', views.toggle_applets, name="toggle_applets"),
|
||||
]
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.http import HttpResponse, HttpResponseForbidden
|
||||
from django.shortcuts import redirect, render
|
||||
|
||||
from .forms import ExistingListItemForm, ItemForm
|
||||
from .models import Item, List
|
||||
from apps.dashboard.forms import ExistingListItemForm, ItemForm
|
||||
from apps.dashboard.models import Applet, Item, List, UserApplet
|
||||
from apps.lyric.models import User
|
||||
|
||||
|
||||
UNLOCKED_THEMES = frozenset(["theme-default"])
|
||||
THEMES = [
|
||||
{"name": "theme-default", "label": "Earthman", "locked": False},
|
||||
{"name": "theme-nirvana", "label": "Nirvana", "locked": True},
|
||||
{"name": "theme-sheol", "label": "Sheol", "locked": True},
|
||||
UNLOCKED_PALETTES = frozenset(["palette-default"])
|
||||
PALETTES = [
|
||||
{"name": "palette-default", "label": "Earthman", "locked": False},
|
||||
{"name": "palette-nirvana", "label": "Nirvana", "locked": True},
|
||||
{"name": "palette-sheol", "label": "Sheol", "locked": True},
|
||||
]
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ def home_page(request):
|
||||
return render(
|
||||
request, "apps/dashboard/home.html", {
|
||||
"form": ItemForm(),
|
||||
"themes": THEMES,
|
||||
"palettes": PALETTES,
|
||||
})
|
||||
|
||||
def new_list(request):
|
||||
@@ -74,12 +74,12 @@ def share_list(request, list_id):
|
||||
return redirect(our_list)
|
||||
|
||||
@login_required(login_url="/")
|
||||
def set_theme(request):
|
||||
def set_palette(request):
|
||||
if request.method == "POST":
|
||||
theme = request.POST.get("theme", "")
|
||||
if theme in UNLOCKED_THEMES:
|
||||
request.user.theme = theme
|
||||
request.user.save(update_fields=["theme"])
|
||||
palette = request.POST.get("palette", "")
|
||||
if palette in UNLOCKED_PALETTES:
|
||||
request.user.palette = palette
|
||||
request.user.save(update_fields=["palette"])
|
||||
return redirect("home")
|
||||
|
||||
@login_required(login_url="/")
|
||||
@@ -89,3 +89,16 @@ def set_profile(request):
|
||||
request.user.username = username
|
||||
request.user.save(update_fields=["username"])
|
||||
return redirect("/")
|
||||
|
||||
@login_required(login_url="/")
|
||||
def toggle_applets(request):
|
||||
checked = request.POST.getlist("applets")
|
||||
for applet in Applet.objects.all():
|
||||
UserApplet.objects.update_or_create(
|
||||
user=request.user,
|
||||
applet=applet,
|
||||
defaults={"visible": applet.slug in checked},
|
||||
)
|
||||
if request.headers.get("HX-Request"):
|
||||
return HttpResponse("")
|
||||
return redirect("home")
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 6.0 on 2026-03-05 19:01
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('lyric', '0003_user_theme'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='user',
|
||||
name='theme',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='palette',
|
||||
field=models.CharField(default='palette-default', max_length=32),
|
||||
),
|
||||
]
|
||||
@@ -26,7 +26,7 @@ class User(AbstractBaseUser):
|
||||
email = models.EmailField(unique=True)
|
||||
username = models.CharField(max_length=35, unique=True, null=True, blank=True)
|
||||
searchable = models.BooleanField(default=False)
|
||||
theme = models.CharField(max_length=32, default="theme-default")
|
||||
palette = models.CharField(max_length=32, default="palette-default")
|
||||
|
||||
is_staff = models.BooleanField(default=False)
|
||||
is_superuser = models.BooleanField(default=False)
|
||||
|
||||
@@ -51,7 +51,7 @@ class UserManagerTest(TestCase):
|
||||
)
|
||||
self.assertTrue(user.check_password("correct-password"))
|
||||
|
||||
class UserThemeTest(TestCase):
|
||||
def test_theme_field_defaults_to_theme_default(self):
|
||||
class UserPaletteTest(TestCase):
|
||||
def test_palette_field_defaults_to_palette_default(self):
|
||||
user = User.objects.create(email="a@b.cde")
|
||||
self.assertEqual(user.theme, "theme-default")
|
||||
self.assertEqual(user.palette, "palette-default")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
def user_theme(request):
|
||||
def user_palette(request):
|
||||
if request.user.is_authenticated:
|
||||
return {"user_theme": request.user.theme}
|
||||
return {"user_theme": "theme-default"}
|
||||
return {"user_palette": request.user.palette}
|
||||
return {"user_palette": "palette-default"}
|
||||
@@ -92,7 +92,7 @@ TEMPLATES = [
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'core.context_processors.user_theme',
|
||||
'core.context_processors.user_palette',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@ from .base import FunctionalTest
|
||||
|
||||
|
||||
class SiteThemeTest(FunctionalTest):
|
||||
def test_page_renders_with_earthman_theme(self):
|
||||
def test_page_renders_with_earthman_palette(self):
|
||||
self.browser.get(self.live_server_url)
|
||||
body = self.browser.find_element(By.TAG_NAME, "body")
|
||||
self.assertIn("theme-default", body.get_attribute("class"))
|
||||
self.assertIn("palette-default", body.get_attribute("class"))
|
||||
@@ -43,9 +43,9 @@ class DashboardMaintenanceTest(FunctionalTest):
|
||||
# 1. Auth as discoman@example.com, navigate home
|
||||
self.create_pre_authenticated_session("discoman@example.com")
|
||||
self.browser.get(self.live_server_url)
|
||||
# 2. Assert both applets present on page (id_applet_username, id_applet_theme_switcher)
|
||||
# 2. Assert both applets present on page (id_applet_username, id_applet_palette)
|
||||
self.browser.find_element(By.ID, "id_applet_username")
|
||||
self.browser.find_element(By.ID, "id_applet_theme_switcher")
|
||||
self.browser.find_element(By.ID, "id_applet_palette")
|
||||
# 3. Click el w. id="id_dash_gear"
|
||||
dash_gear = self.browser.find_element(By.ID, "id_dash_gear")
|
||||
dash_gear.click()
|
||||
@@ -53,15 +53,15 @@ class DashboardMaintenanceTest(FunctionalTest):
|
||||
self.wait_for(
|
||||
lambda: self.browser.find_element(By.ID, "id_applet_menu")
|
||||
)
|
||||
# 5. Find two checkboxes in menu, name="username" & name="theme-switcher"; 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")
|
||||
username_cb = menu.find_element(By.NAME, "username")
|
||||
theme_cb = menu.find_element(By.NAME, "theme-switcher")
|
||||
palette_cb = menu.find_element(By.NAME, "palette")
|
||||
self.assertTrue(username_cb.is_selected())
|
||||
self.assertTrue(theme_cb.is_selected())
|
||||
# 6. Click theme-switcher box to uncheck it
|
||||
theme_cb.click()
|
||||
self.assertFalse(theme_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")
|
||||
# 7. Submit the menu form via [type="submit"] btn inside menu
|
||||
menu.find_element(By.CSS_SELECTOR, '[type="submit"]').click()
|
||||
@@ -70,24 +70,24 @@ class DashboardMaintenanceTest(FunctionalTest):
|
||||
self.browser.find_element(By.ID, "id_applet_menu").is_displayed()
|
||||
)
|
||||
)
|
||||
# 8. wait_for theme-switcher applet to be gone
|
||||
# 8. wait_for palette applet to be gone
|
||||
self.wait_for(
|
||||
lambda: self.assertRaises(
|
||||
NoSuchElementException,
|
||||
self.browser.find_element,
|
||||
By.ID, "id_applet_theme_switcher"
|
||||
By.ID, "id_applet_palette"
|
||||
)
|
||||
)
|
||||
# 9. assert id_applet_username remains
|
||||
self.browser.find_element(By.ID, "id_applet_username")
|
||||
# 10. Click gear again, find menu, find theme-switcher checkbox; assert now NOT selected
|
||||
# 10. Click gear again, find menu, find palette checkbox; assert now NOT selected
|
||||
dash_gear.click()
|
||||
self.assertFalse(theme_cb.is_selected())
|
||||
self.assertFalse(palette_cb.is_selected())
|
||||
# 11. Click it to re-check box; submit
|
||||
theme_cb.click()
|
||||
self.assertTrue(theme_cb.is_selected())
|
||||
palette_cb.click()
|
||||
self.assertTrue(palette_cb.is_selected())
|
||||
menu.find_element(By.CSS_SELECTOR, '[type="submit"]').click()
|
||||
# 12. wait_for id_applet_theme_switcher to reappear
|
||||
# 12. wait_for id_applet_palette to reappear
|
||||
self.wait_for(
|
||||
lambda: self.assertFalse(
|
||||
self.browser.find_element(By.ID, "id_applet_menu").is_displayed()
|
||||
@@ -95,7 +95,7 @@ class DashboardMaintenanceTest(FunctionalTest):
|
||||
)
|
||||
self.wait_for(
|
||||
lambda: self.assertTrue(
|
||||
self.browser.find_element(By.ID, "id_applet_theme_switcher")
|
||||
self.browser.find_element(By.ID, "id_applet_palette")
|
||||
)
|
||||
)
|
||||
self.assertTrue(self.browser.execute_script("return window.__no_reload_marker === true"))
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
.theme-picker {
|
||||
.palette-picker {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.theme-picker-item {
|
||||
.palette-picker-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -1,7 +1,7 @@
|
||||
@import 'rootvars';
|
||||
@import 'base';
|
||||
@import 'button-pad';
|
||||
@import 'theme-picker';
|
||||
@import 'palette-picker';
|
||||
|
||||
|
||||
input,
|
||||
|
||||
@@ -301,8 +301,8 @@
|
||||
--decClh: 255, 174, 0;
|
||||
}
|
||||
|
||||
/* Default Earthman Theme */
|
||||
.theme-default {
|
||||
/* Default Earthman Palette */
|
||||
.palette-default {
|
||||
--priUser: var(--terBrk);
|
||||
--secUser: var(--priKhk);
|
||||
--terUser: var(--priMze);
|
||||
@@ -314,8 +314,8 @@
|
||||
--ninUser: var(--priCtn);
|
||||
--decUser: var(--terCtn);
|
||||
}
|
||||
/* Grave Sheol Theme */
|
||||
.theme-sheol {
|
||||
/* Grave Sheol Palette */
|
||||
.palette-sheol {
|
||||
--priUser: var(--priPu);
|
||||
--secUser: var(--quiPu);
|
||||
--terUser: var(--terFs);
|
||||
@@ -327,8 +327,8 @@
|
||||
--ninUser: var(--sixPu);
|
||||
--decUser: var(--terPu);
|
||||
}
|
||||
/* Blissful Nirvana Theme */
|
||||
.theme-nirvana {
|
||||
/* Blissful Nirvana Palette */
|
||||
.palette-nirvana {
|
||||
--priUser: var(--priU);
|
||||
--secUser: var(--quiU);
|
||||
--terUser: var(--terMe);
|
||||
@@ -340,8 +340,8 @@
|
||||
--ninUser: var(--sixCu);
|
||||
--decUser: var(--terU);
|
||||
}
|
||||
/* Disco Inferno Theme */
|
||||
.theme-inferno {
|
||||
/* Disco Inferno Palette */
|
||||
.palette-inferno {
|
||||
--priUser: var(--quaSwp);
|
||||
--secUser: var(--priSwp);
|
||||
--terUser: var(--terBld);
|
||||
@@ -353,8 +353,8 @@
|
||||
--ninUser: var(--priMst);
|
||||
--decUser: var(--terMst);
|
||||
}
|
||||
/* Torre Terrestre Theme */
|
||||
.theme-terrestre {
|
||||
/* Torre Terrestre Palette */
|
||||
.palette-terrestre {
|
||||
--priUser: var(--priAdm);
|
||||
--secUser: var(--quaAdm);
|
||||
--terUser: var(--sixAdm);
|
||||
@@ -366,8 +366,8 @@
|
||||
--ninUser: var(--sixPer);
|
||||
--decUser: var(--terMrb);
|
||||
}
|
||||
/* Fantastia Celestia Theme */
|
||||
.theme-celestia {
|
||||
/* Fantastia Celestia Palette */
|
||||
.palette-celestia {
|
||||
--priUser: var(--octClh);
|
||||
--secUser: var(--sixClh);
|
||||
--terUser: var(--quaClh);
|
||||
@@ -380,7 +380,7 @@
|
||||
--decUser: var(--quiClh);
|
||||
}
|
||||
|
||||
/* Theme Classes */
|
||||
/* Palette Classes */
|
||||
.priUser {
|
||||
color: rgba(var(--priUser), 1);
|
||||
}
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
|
||||
{% block content %}
|
||||
{% if user.is_authenticated %}
|
||||
<section id="id_applet_theme_switcher" class="theme-picker">
|
||||
{% for theme in themes %}
|
||||
<div class="theme-picker-item">
|
||||
<div class="swatch {{ theme.name }}{% if user_theme == theme.name %} active{% endif %}{% if theme.locked %} locked{% endif %}"></div>
|
||||
{% if not theme.locked %}
|
||||
<form method="POST" action="{% url 'set_theme' %}">
|
||||
<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="theme" value="{{ theme.name }}" class="btn btn-confirm">OK</button>
|
||||
<button type="submit" name="palette" value="{{ palette.name }}" class="btn btn-confirm">OK</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<span class="btn btn-disabled">×</span>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
{% endcompress %}
|
||||
</head>
|
||||
|
||||
<body class="{{ user_theme }}">
|
||||
<body class="{{ user_palette }}">
|
||||
<div class="container">
|
||||
<nav class="navbar">
|
||||
<div class="container-fluid">
|
||||
|
||||
Reference in New Issue
Block a user