rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard

- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-04-22 22:32:34 -04:00
parent 6d9d3d4f54
commit 473e6bc45a
54 changed files with 1373 additions and 1283 deletions

View File

@@ -15,14 +15,14 @@ from django.utils import timezone
from django.views.decorators.csrf import ensure_csrf_cookie
from apps.applets.utils import applet_context, apply_applet_toggle
from apps.dashboard.forms import ExistingNoteItemForm, ItemForm
from apps.dashboard.models import Item, Note
from apps.drama.models import Recognition
from apps.dashboard.forms import ExistingPostLineForm, LineForm
from apps.dashboard.models import Line, Post
from apps.drama.models import Note
from apps.epic.utils import _compute_distinctions
from apps.lyric.models import PaymentMethod, Token, User, Wallet
APPLET_ORDER = ["wallet", "new-note", "my-notes", "username", "palette"]
APPLET_ORDER = ["wallet", "username", "palette"]
_BASE_UNLOCKED = frozenset([
"palette-default",
"palette-cedar",
@@ -40,7 +40,7 @@ _PALETTE_DEFS = [
{"name": "palette-terrestre", "label": "Terrestre", "locked": True},
{"name": "palette-celestia", "label": "Celestia", "locked": True},
]
_RECOGNITION_TITLES = {
_NOTE_TITLES = {
"stargazer": "Stargazer",
"schizo": "Schizo",
"nomad": "Nomad",
@@ -54,7 +54,7 @@ def _palettes_for_user(user):
return [dict(p, shoptalk="Placeholder") for p in _PALETTE_DEFS]
granted = {
r.palette: r
for r in Recognition.objects.filter(user=user, palette__isnull=False).exclude(palette="")
for r in Note.objects.filter(user=user, palette__isnull=False).exclude(palette="")
}
result = []
for p in _PALETTE_DEFS:
@@ -62,7 +62,7 @@ def _palettes_for_user(user):
r = granted.get(p["name"])
if r and p["locked"]:
entry["locked"] = False
title = _RECOGNITION_TITLES.get(r.slug, r.slug.capitalize())
title = _NOTE_TITLES.get(r.slug, r.slug.capitalize())
entry["shoptalk"] = f"{title} · {r.earned_at.strftime('%b %d, %Y').replace(' 0', ' ')}"
else:
entry["shoptalk"] = "Placeholder"
@@ -73,89 +73,86 @@ def _palettes_for_user(user):
def _unlocked_palettes_for_user(user):
base = set(_BASE_UNLOCKED)
if user and user.is_authenticated:
for r in Recognition.objects.filter(user=user, palette__isnull=False).exclude(palette=""):
for r in Note.objects.filter(user=user, palette__isnull=False).exclude(palette=""):
base.add(r.palette)
return base
def _recent_notes(user, limit=3):
def _recent_posts(user, limit=3):
return (
Note
Post
.objects
.filter(Q(owner=user) | Q(shared_with=user))
.annotate(last_item=Max('item__id'))
.order_by('-last_item')
.annotate(last_line=Max('lines__id'))
.order_by('-last_line')
.distinct()[:limit]
)
def home_page(request):
context = {
"form": ItemForm(),
"palettes": _palettes_for_user(request.user),
"page_class": "page-dashboard",
}
if request.user.is_authenticated:
context["applets"] = applet_context(request.user, "dashboard")
context["recent_notes"] = _recent_notes(request.user)
return render(request, "apps/dashboard/home.html", context)
def new_note(request):
form = ItemForm(data=request.POST)
def new_post(request):
form = LineForm(data=request.POST)
if form.is_valid():
nunote = Note.objects.create()
nupost = Post.objects.create()
if request.user.is_authenticated:
nunote.owner = request.user
nunote.save()
form.save(for_note=nunote)
return redirect(nunote)
nupost.owner = request.user
nupost.save()
form.save(for_post=nupost)
return redirect(nupost)
else:
context = {
"form": form,
"palettes": _palettes_for_user(request.user),
"page_class": "page-dashboard",
"page_class": "page-billboard",
}
if request.user.is_authenticated:
context["applets"] = applet_context(request.user, "dashboard")
context["recent_notes"] = _recent_notes(request.user)
return render(request, "apps/dashboard/home.html", context)
context["applets"] = applet_context(request.user, "billboard")
context["recent_posts"] = _recent_posts(request.user)
return render(request, "apps/billboard/billboard.html", context)
def view_note(request, note_id):
our_note = Note.objects.get(id=note_id)
def view_post(request, post_id):
our_post = Post.objects.get(id=post_id)
if our_note.owner:
if our_post.owner:
if not request.user.is_authenticated:
return redirect("/")
if request.user != our_note.owner and request.user not in our_note.shared_with.all():
if request.user != our_post.owner and request.user not in our_post.shared_with.all():
return HttpResponseForbidden()
form = ExistingNoteItemForm(for_note=our_note)
form = ExistingPostLineForm(for_post=our_post)
if request.method == "POST":
form = ExistingNoteItemForm(for_note=our_note, data=request.POST)
form = ExistingPostLineForm(for_post=our_post, data=request.POST)
if form.is_valid():
form.save()
return redirect(our_note)
return render(request, "apps/dashboard/note.html", {"note": our_note, "form": form})
return redirect(our_post)
return render(request, "apps/dashboard/post.html", {"post": our_post, "form": form})
def my_notes(request, user_id):
def my_posts(request, user_id):
owner = User.objects.get(id=user_id)
if not request.user.is_authenticated:
return redirect("/")
if request.user.id != owner.id:
return HttpResponseForbidden()
return render(request, "apps/dashboard/my_notes.html", {"owner": owner})
return render(request, "apps/dashboard/my_posts.html", {"owner": owner})
def share_note(request, note_id):
our_note = Note.objects.get(id=note_id)
def share_post(request, post_id):
our_post = Post.objects.get(id=post_id)
try:
recipient = User.objects.get(email=request.POST["recipient"])
if recipient == request.user:
return redirect(our_note)
our_note.shared_with.add(recipient)
return redirect(our_post)
our_post.shared_with.add(recipient)
except User.DoesNotExist:
pass
messages.success(request, "An invite has been sent if that address is registered.")
return redirect(our_note)
return redirect(our_post)
@login_required(login_url="/")
def set_palette(request):
@@ -184,8 +181,6 @@ def toggle_applets(request):
return render(request, "apps/dashboard/_partials/_applets.html", {
"applets": applet_context(request.user, "dashboard"),
"palettes": _palettes_for_user(request.user),
"form": ItemForm(),
"recent_notes": _recent_notes(request.user),
})
return redirect("home")
@@ -410,18 +405,18 @@ def sky_save(request):
'sky_birth_place', 'sky_birth_tz', 'sky_house_system', 'sky_chart_data',
])
recognition_payload = None
note_payload = None
if user.sky_chart_data:
recog, created = Recognition.grant_if_new(user, "stargazer")
note, created = Note.grant_if_new(user, "stargazer")
if created:
recognition_payload = {
"slug": recog.slug,
note_payload = {
"slug": note.slug,
"title": "Stargazer",
"description": "You saved your first personal sky chart.",
"earned_at": recog.earned_at.isoformat(),
"earned_at": note.earned_at.isoformat(),
}
return JsonResponse({"saved": True, "recognition": recognition_payload})
return JsonResponse({"saved": True, "note": note_payload})
@login_required(login_url="/")