new applets app for cross-board usage of Applet() & UserApplet() models; dashboard migrations reset and apps reseeded w. new default specs; core.settings & many tests thru-out suite updated accordingly
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Disco DeDisco
2026-03-09 16:08:28 -04:00
parent 2c445c0e76
commit 97601586c5
26 changed files with 142 additions and 183 deletions

View File

11
src/apps/applets/admin.py Normal file
View File

@@ -0,0 +1,11 @@
from django.contrib import admin
from apps.applets.models import Applet, UserApplet
@admin.register(Applet)
class AppletAdmin(admin.ModelAdmin):
list_display = ['slug', 'name', 'default_visible', 'grid_cols', 'grid_rows']
list_editable = ['grid_cols', 'grid_rows']
admin.site.register(UserApplet)

5
src/apps/applets/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class AppletsConfig(AppConfig):
name = 'apps.applets'

View File

@@ -1,14 +1,12 @@
# Generated by Django 6.0 on 2026-03-04 20:34
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True
dependencies = [ dependencies = [
('dashboard', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
@@ -19,7 +17,10 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(unique=True)), ('slug', models.SlugField(unique=True)),
('name', models.CharField(max_length=100)), ('name', models.CharField(max_length=100)),
('context', models.CharField(choices=[('dashboard', 'Dashboard'), ('gameboard', 'Gameboard')], default='dashboard', max_length=20)),
('default_visible', models.BooleanField(default=True)), ('default_visible', models.BooleanField(default=True)),
('grid_cols', models.PositiveSmallIntegerField(default=12)),
('grid_rows', models.PositiveSmallIntegerField(default=3)),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
@@ -27,11 +28,9 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('visible', models.BooleanField(default=True)), ('visible', models.BooleanField(default=True)),
('applet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dashboard.applet')), ('applet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='applets.applet')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_applets', to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_applets', to=settings.AUTH_USER_MODEL)),
], ],
options={ options={'unique_together': {('user', 'applet')}},
'unique_together': {('user', 'applet')},
},
), ),
] ]

View File

@@ -0,0 +1,29 @@
from django.db import migrations
def seed_applets(apps, schema_editor):
Applet = apps.get_model('applets', 'Applet')
for slug, name, cols, rows, context in [
('wallet', 'Wallet', 12, 3, 'dashboard'),
('new-list', 'New List', 9, 3, 'dashboard'),
('my-lists', 'My Lists', 3, 3, 'dashboard'),
('username', 'Username', 6, 3, 'dashboard'),
('palette', 'Palette', 6, 3, 'dashboard'),
('new-game', 'New Game', 4, 2, 'gameboard'),
('my-games', 'My Games', 4, 4, 'gameboard'),
('game-kit', 'Game Kit', 4, 2, 'gameboard'),
]:
Applet.objects.get_or_create(
slug=slug,
defaults={'name': name, 'grid_cols': cols, 'grid_rows': rows, 'context': context},
)
class Migration(migrations.Migration):
dependencies = [
('applets', '0001_initial')
]
operations = [
migrations.RunPython(seed_applets, migrations.RunPython.noop)
]

View File

View File

@@ -0,0 +1,34 @@
from django.db import models
class Applet(models.Model):
DASHBOARD = "dashboard"
GAMEBOARD = "gameboard"
CONTEXT_CHOICES = [
(DASHBOARD, "Dashboard"),
(GAMEBOARD, "Gameboard"),
]
slug = models.SlugField(unique=True)
name = models.CharField(max_length=100)
context = models.CharField(max_length=20, choices=CONTEXT_CHOICES, default=DASHBOARD)
default_visible = models.BooleanField(default=True)
grid_cols = models.PositiveSmallIntegerField(default=12)
grid_rows = models.PositiveSmallIntegerField(default=3)
def __str__(self):
return self.name
class UserApplet(models.Model):
user = models.ForeignKey(
"lyric.User",
related_name="user_applets",
on_delete=models.CASCADE,
)
applet = models.ForeignKey(
Applet,
on_delete=models.CASCADE,
)
visible = models.BooleanField(default=True)
class Meta:
unique_together = ("user", "applet")

View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1,40 @@
from django.db.utils import IntegrityError
from django.test import TestCase
from apps.applets.models import Applet, UserApplet
from apps.lyric.models import User
class AppletModelTest(TestCase):
def setUp(self):
self.applet = Applet.objects.create(
slug="my-applet", name="My Applet", default_visible=True
)
def test_applet_can_be_created(self):
self.assertEqual(Applet.objects.get(slug="my-applet"), self.applet)
def test_applet_slug_is_unique(self):
with self.assertRaises(IntegrityError):
Applet.objects.create(slug="my-applet", name="Second")
def test_applet_str(self):
self.assertEqual(str(self.applet), "My Applet")
def test_applet_grid_defaults(self):
self.assertEqual(self.applet.grid_cols, 12)
self.assertEqual(self.applet.grid_rows, 3)
class UserAppletModelTest(TestCase):
def setUp(self):
self.user = User.objects.create(email="a@b.cde")
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)
self.assertIn(ua, self.user.user_applets.all())
def test_user_applet_unique_per_user_and_applet(self):
UserApplet.objects.create(user=self.user, applet=self.applet, visible=True)
with self.assertRaises(IntegrityError):
UserApplet.objects.create(user=self.user, applet=self.applet, visible=False)

View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@@ -1,11 +1 @@
from django.contrib import admin from django.contrib import admin
from apps.dashboard.models import Applet, UserApplet
@admin.register(Applet)
class AppletAdmin(admin.ModelAdmin):
list_display = ['slug', 'name', 'default_visible', 'grid_cols', 'grid_rows']
list_editable = ['grid_cols', 'grid_rows']
admin.site.register(UserApplet)

View File

@@ -1,17 +0,0 @@
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"})
class Migration(migrations.Migration):
dependencies = [
("dashboard", "0002_applet_userapplet"),
]
operations = [
migrations.RunPython(seed_applets, migrations.RunPython.noop),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 6.0 on 2026-03-06 22:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0003_seed_applets'),
]
operations = [
migrations.AddField(
model_name='applet',
name='grid_cols',
field=models.PositiveSmallIntegerField(default=12),
),
migrations.AddField(
model_name='applet',
name='grid_rows',
field=models.PositiveSmallIntegerField(default=3),
),
]

View File

@@ -1,17 +0,0 @@
from django.db import migrations
def set_grid_defaults(apps, schema_editor):
Applet = apps.get_model("dashboard", "Applet")
Applet.objects.filter(slug__in=["username", "theme-switcher"]).update(grid_cols=6, grid_rows=3)
Applet.objects.get_or_create(slug="new-list", defaults={"name": "New List", "grid_cols": 9, "grid_rows": 3})
Applet.objects.get_or_create(slug="my-lists", defaults={"name": "My Lists", "grid_cols": 3, "grid_rows": 3})
class Migration(migrations.Migration):
dependencies = [
("dashboard", "0004_applet_grid_cols_applet_grid_rows"),
]
operations = [
migrations.RunPython(set_grid_defaults, migrations.RunPython.noop),
]

View File

@@ -1,18 +0,0 @@
from django.db import migrations
def rename_theme_switcher(apps, schema_editor):
Applet = apps.get_model("dashboard", "Applet")
Applet.objects.filter(slug="theme-switcher").update(
slug="palette", name="Palette", grid_cols=6, grid_rows=3
)
class Migration(migrations.Migration):
dependencies = [
("dashboard", "0005_set_applet_grid_defaults"),
]
operations = [
migrations.RunPython(rename_theme_switcher, migrations.RunPython.noop),
]

View File

@@ -1,18 +0,0 @@
from django.db import migrations
def seed_wallet_applet(apps, schema_editor):
Applet = apps.get_model("dashboard", "Applet")
Applet.objects.get_or_create(
slug="wallet",
defaults={"name": "Wallet", "grid_cols": 12, "grid_rows": 3},
)
class Migration(migrations.Migration):
dependencies = [
("dashboard", "0006_rename_theme_switcher"),
]
operations = [
migrations.RunPython(seed_wallet_applet, migrations.RunPython.noop),
]

View File

@@ -37,29 +37,3 @@ class Item(models.Model):
def __str__(self): def __str__(self):
return self.text return self.text
class Applet(models.Model):
slug = models.SlugField(unique=True)
name = models.CharField(max_length=100)
default_visible = models.BooleanField(default=True)
grid_cols = models.PositiveSmallIntegerField(default=12)
grid_rows = models.PositiveSmallIntegerField(default=3)
def __str__(self):
return self.name
class UserApplet(models.Model):
user = models.ForeignKey(
"lyric.User",
related_name="user_applets",
on_delete=models.CASCADE,
)
applet = models.ForeignKey(
Applet,
on_delete=models.CASCADE,
)
visible = models.BooleanField(default=True)
class Meta:
unique_together = ("user", "applet")

View File

@@ -2,7 +2,7 @@ from django.core.exceptions import ValidationError
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from django.test import TestCase from django.test import TestCase
from apps.dashboard.models import Applet, Item, List, UserApplet from apps.dashboard.models import Item, List
from apps.lyric.models import User from apps.lyric.models import User
@@ -68,37 +68,3 @@ class ListModelTest(TestCase):
Item.objects.create(list=list_, text="first item") Item.objects.create(list=list_, text="first item")
Item.objects.create(list=list_, text="second item") Item.objects.create(list=list_, text="second item")
self.assertEqual(list_.name, "first item") self.assertEqual(list_.name, "first item")
class AppletModelTest(TestCase):
def setUp(self):
self.applet = Applet.objects.create(
slug="my-applet", name="My Applet", default_visible=True
)
def test_applet_can_be_created(self):
self.assertEqual(Applet.objects.get(slug="my-applet"), self.applet)
def test_applet_slug_is_unique(self):
with self.assertRaises(IntegrityError):
Applet.objects.create(slug="my-applet", name="Second")
def test_applet_str(self):
self.assertEqual(str(self.applet), "My Applet")
def test_applet_grid_defaults(self):
self.assertEqual(self.applet.grid_cols, 12)
self.assertEqual(self.applet.grid_rows, 3)
class UserAppletModelTest(TestCase):
def setUp(self):
self.user = User.objects.create(email="a@b.cde")
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)
self.assertIn(ua, self.user.user_applets.all())
def test_user_applet_unique_per_user_and_applet(self):
UserApplet.objects.create(user=self.user, applet=self.applet, visible=True)
with self.assertRaises(IntegrityError):
UserApplet.objects.create(user=self.user, applet=self.applet, visible=False)

View File

@@ -5,11 +5,12 @@ from django.test import override_settings, TestCase
from django.urls import reverse from django.urls import reverse
from django.utils import html from django.utils import html
from apps.applets.models import Applet, UserApplet
from apps.dashboard.forms import ( from apps.dashboard.forms import (
DUPLICATE_ITEM_ERROR, DUPLICATE_ITEM_ERROR,
EMPTY_ITEM_ERROR, EMPTY_ITEM_ERROR,
) )
from apps.dashboard.models import Applet, Item, List, UserApplet from apps.dashboard.models import Item, List
from apps.lyric.models import User from apps.lyric.models import User

View File

@@ -8,8 +8,9 @@ from django.http import HttpResponse, HttpResponseForbidden, JsonResponse
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from apps.applets.models import Applet, UserApplet
from apps.dashboard.forms import ExistingListItemForm, ItemForm from apps.dashboard.forms import ExistingListItemForm, ItemForm
from apps.dashboard.models import Applet, Item, List, UserApplet from apps.dashboard.models import Item, List
from apps.lyric.models import PaymentMethod, Token, User, Wallet from apps.lyric.models import PaymentMethod, Token, User, Wallet

View File

@@ -62,6 +62,7 @@ INSTALLED_APPS = [
'apps.lyric', 'apps.lyric',
# Custom apps # Custom apps
'apps.api', 'apps.api',
'apps.applets',
'functional_tests', 'functional_tests',
# Depend apps # Depend apps
'compressor', 'compressor',

View File

@@ -8,11 +8,10 @@ from pathlib import Path
from selenium import webdriver from selenium import webdriver
from selenium.common.exceptions import WebDriverException from selenium.common.exceptions import WebDriverException
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from .container_commands import create_session_on_server, reset_database from .container_commands import create_session_on_server, reset_database
from .management.commands.create_session import create_pre_authenticated_session from .management.commands.create_session import create_pre_authenticated_session
from apps.dashboard.models import Applet from apps.applets.models import Applet

View File

@@ -3,7 +3,7 @@ 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 from apps.applets.models import Applet
class DashboardMaintenanceTest(FunctionalTest): class DashboardMaintenanceTest(FunctionalTest):

View File

@@ -1,6 +1,3 @@
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from .base import FunctionalTest from .base import FunctionalTest
from .list_page import ListPage from .list_page import ListPage

View File

@@ -3,7 +3,6 @@ from selenium.webdriver.common.by import By
from .base import FunctionalTest from .base import FunctionalTest
from .list_page import ListPage from .list_page import ListPage
from .my_lists_page import MyListsPage from .my_lists_page import MyListsPage
from apps.lyric.models import User
class MyListsTest(FunctionalTest): class MyListsTest(FunctionalTest):

View File

@@ -2,7 +2,7 @@ from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from .base import FunctionalTest from .base import FunctionalTest
from apps.dashboard.models import Applet from apps.applets.models import Applet
class WalletDisplayTest(FunctionalTest): class WalletDisplayTest(FunctionalTest):