pronouns: per-user pronouns ideology + Pronouns applet on Game Kit; provenance prose uses actor.pronouns at render time — TDD
- User.pronouns CharField w. choices=[pluralism (default), bawlmorese, misogyny, misandry, misanthropy] + pronoun_subj/obj/poss properties; PRONOUN_TABLE single source of truth in apps.lyric.models; mig 0002_user_pronouns
- drama.GameEvent.to_prose() drops module-level PRONOUN_* constants; SIG_READY/SIG_UNREADY/ROLE_SELECTED now resolve poss/subj from self.actor.pronouns at render time, so flipping a user's preference rewrites all their existing scroll prose; default actor → "their"
- SIG_READY prose strips a leading "The " from card_name so "the The Wanderer" reads "the Wanderer" and "the Engraven The Nomad" reads "the Engraven Nomad"; minor arcana ("Maid of Brands") untouched
- new applets/0005 seeds 'pronouns' applet (3x3, game-kit, default visible); _game_kit_sections.html grows a #id_gk_pronouns block w. 5 .gk-pronoun-card items labeled by ideology slug (italic) and tagged data-pronoun + data-trio
- card click → window.showGuard(card, "Set pronoun preference?<span class='guard-pronoun-trio'>{trio}</span>", commitCb); on OK fetches POST /dashboard/set-pronouns w. CSRF cookie + reloads so .active class moves and provenance prose re-renders; NVM dismisses
- dashboard.set_pronouns view (POST-only, login_required, 204/400/405) at /dashboard/set-pronouns; rejects choices not in PRONOUN_TABLE
- _game-kit.scss extends shared card rule to .gk-pronoun-card w. .active fill state + italic ideology label; #id_guard_portal .guard-pronoun-trio styled small/dim/centered under the question
- billscroll aperture: padding-right 0.75rem on #id_drama_scroll inside .applet-scroll so the timestamp column no longer sits beneath the scrollbar
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
18
src/apps/lyric/migrations/0002_user_pronouns.py
Normal file
18
src/apps/lyric/migrations/0002_user_pronouns.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0 on 2026-05-04 04:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('lyric', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='pronouns',
|
||||
field=models.CharField(choices=[('pluralism', 'they/them/their'), ('bawlmorese', 'yo/yo/yos'), ('misogyny', 'he/him/his'), ('misandry', 'she/her/hers'), ('misanthropy', 'it/it/its')], default='pluralism', max_length=16),
|
||||
),
|
||||
]
|
||||
@@ -9,6 +9,34 @@ from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
# ── Pronoun preference set ────────────────────────────────────────────────
|
||||
# Drives provenance prose ("embodies as their Significator …") and any other
|
||||
# user-facing referent. Default is pluralism (singular they) so a brand-new
|
||||
# account renders neutrally; bawlmorese (yo/yo/yos) is the original Earthman
|
||||
# default kept available as a Baltimore-flavoured option.
|
||||
|
||||
PRONOUN_CHOICES = [
|
||||
("pluralism", "they/them/their"),
|
||||
("bawlmorese", "yo/yo/yos"),
|
||||
("misogyny", "he/him/his"),
|
||||
("misandry", "she/her/hers"),
|
||||
("misanthropy", "it/it/its"),
|
||||
]
|
||||
PRONOUN_TABLE = {
|
||||
"pluralism": {"subj": "they", "obj": "them", "poss": "their"},
|
||||
"bawlmorese": {"subj": "yo", "obj": "yo", "poss": "yos"},
|
||||
"misogyny": {"subj": "he", "obj": "him", "poss": "his"},
|
||||
"misandry": {"subj": "she", "obj": "her", "poss": "hers"},
|
||||
"misanthropy": {"subj": "it", "obj": "it", "poss": "its"},
|
||||
}
|
||||
|
||||
|
||||
def resolve_pronouns(pronouns_key):
|
||||
"""Return (subj, obj, poss) for a pronouns key, defaulting to pluralism."""
|
||||
row = PRONOUN_TABLE.get(pronouns_key) or PRONOUN_TABLE["pluralism"]
|
||||
return row["subj"], row["obj"], row["poss"]
|
||||
|
||||
|
||||
class UserManager(BaseUserManager):
|
||||
def create_user(self, email):
|
||||
user = self.model(email=email)
|
||||
@@ -63,10 +91,26 @@ class User(AbstractBaseUser):
|
||||
is_staff = models.BooleanField(default=False)
|
||||
is_superuser = models.BooleanField(default=False)
|
||||
|
||||
pronouns = models.CharField(
|
||||
max_length=16, choices=PRONOUN_CHOICES, default="pluralism",
|
||||
)
|
||||
|
||||
objects = UserManager()
|
||||
REQUIRED_FIELDS = []
|
||||
USERNAME_FIELD = "email"
|
||||
|
||||
@property
|
||||
def pronoun_subj(self):
|
||||
return resolve_pronouns(self.pronouns)[0]
|
||||
|
||||
@property
|
||||
def pronoun_obj(self):
|
||||
return resolve_pronouns(self.pronouns)[1]
|
||||
|
||||
@property
|
||||
def pronoun_poss(self):
|
||||
return resolve_pronouns(self.pronouns)[2]
|
||||
|
||||
def ensure_keypair(self):
|
||||
"""Generate and persist an RSA-2048 keypair if not already set."""
|
||||
if self.ap_public_key:
|
||||
|
||||
@@ -79,6 +79,50 @@ class UserPaletteTest(TestCase):
|
||||
user = User.objects.create(email="a@b.cde")
|
||||
self.assertEqual(user.palette, "palette-default")
|
||||
|
||||
|
||||
class UserPronounsTest(TestCase):
|
||||
def test_pronouns_default_to_pluralism(self):
|
||||
user = User.objects.create(email="they@test.io")
|
||||
self.assertEqual(user.pronouns, "pluralism")
|
||||
|
||||
def test_pronouns_choices_include_all_five_options(self):
|
||||
from apps.lyric.models import PRONOUN_CHOICES
|
||||
keys = [k for (k, _) in PRONOUN_CHOICES]
|
||||
self.assertEqual(
|
||||
keys,
|
||||
["pluralism", "bawlmorese", "misogyny", "misandry", "misanthropy"],
|
||||
)
|
||||
|
||||
def test_pluralism_resolves_to_they_them_their(self):
|
||||
user = User.objects.create(email="plural@test.io")
|
||||
self.assertEqual(user.pronoun_subj, "they")
|
||||
self.assertEqual(user.pronoun_obj, "them")
|
||||
self.assertEqual(user.pronoun_poss, "their")
|
||||
|
||||
def test_bawlmorese_resolves_to_yo_yo_yos(self):
|
||||
user = User.objects.create(email="yo@test.io", pronouns="bawlmorese")
|
||||
self.assertEqual(user.pronoun_subj, "yo")
|
||||
self.assertEqual(user.pronoun_obj, "yo")
|
||||
self.assertEqual(user.pronoun_poss, "yos")
|
||||
|
||||
def test_misogyny_resolves_to_he_him_his(self):
|
||||
user = User.objects.create(email="he@test.io", pronouns="misogyny")
|
||||
self.assertEqual(user.pronoun_subj, "he")
|
||||
self.assertEqual(user.pronoun_obj, "him")
|
||||
self.assertEqual(user.pronoun_poss, "his")
|
||||
|
||||
def test_misandry_resolves_to_she_her_hers(self):
|
||||
user = User.objects.create(email="she@test.io", pronouns="misandry")
|
||||
self.assertEqual(user.pronoun_subj, "she")
|
||||
self.assertEqual(user.pronoun_obj, "her")
|
||||
self.assertEqual(user.pronoun_poss, "hers")
|
||||
|
||||
def test_misanthropy_resolves_to_it_it_its(self):
|
||||
user = User.objects.create(email="it@test.io", pronouns="misanthropy")
|
||||
self.assertEqual(user.pronoun_subj, "it")
|
||||
self.assertEqual(user.pronoun_obj, "it")
|
||||
self.assertEqual(user.pronoun_poss, "its")
|
||||
|
||||
class WalletCreationTest(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create(email="capman@test.io")
|
||||
|
||||
Reference in New Issue
Block a user