From 473e6bc45a602bc4ab021a8a0e097f9fb1e5657e Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Wed, 22 Apr 2026 22:32:34 -0400 Subject: [PATCH] =?UTF-8?q?rename:=20Note=E2=86=92Post/Line=20(dashboard);?= =?UTF-8?q?=20Recognition=E2=86=92Note=20(drama);=20new-post/my-posts=20to?= =?UTF-8?q?=20billboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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//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 --- src/apps/api/serializers.py | 18 +- src/apps/api/tests/integrated/test_views.py | 80 ++-- src/apps/api/tests/unit/test_serializers.py | 12 +- src/apps/api/urls.py | 6 +- src/apps/api/views.py | 34 +- .../0011_rename_note_applets_to_post.py | 24 ++ ...0012_rename_recognition_applet_to_notes.py | 31 ++ .../{recognition-page.js => note-page.js} | 26 +- .../billboard/tests/integrated/test_views.py | 62 +-- src/apps/billboard/urls.py | 4 +- src/apps/billboard/views.py | 55 ++- src/apps/dashboard/forms.py | 28 +- .../migrations/0004_rename_note_to_post.py | 18 + src/apps/dashboard/models.py | 16 +- .../dashboard/static/apps/dashboard/note.js | 49 +++ .../static/apps/dashboard/recognition.js | 49 --- .../dashboard/tests/integrated/test_forms.py | 54 +-- .../dashboard/tests/integrated/test_models.py | 94 ++--- .../tests/integrated/test_sky_views.py | 38 +- .../dashboard/tests/integrated/test_views.py | 231 ++++++----- src/apps/dashboard/tests/unit/test_forms.py | 12 +- src/apps/dashboard/tests/unit/test_models.py | 12 +- src/apps/dashboard/urls.py | 8 +- src/apps/dashboard/views.py | 93 +++-- .../0005_rename_recognition_to_note.py | 24 ++ src/apps/drama/models.py | 4 +- .../drama/tests/integrated/test_models.py | 36 +- src/functional_tests/base.py | 4 +- .../{my_notes_page.py => my_posts_page.py} | 4 +- .../{note_page.py => post_page.py} | 28 +- src/functional_tests/test_applet_my_notes.py | 360 ++++++++++++++++-- src/functional_tests/test_applet_my_posts.py | 44 +++ .../test_applet_new_note_item_validation.py | 80 ---- ...et_new_note.py => test_applet_new_post.py} | 48 +-- .../test_applet_new_post_line_validation.py | 80 ++++ src/functional_tests/test_recognition.py | 350 ----------------- .../tests/{RecognitionSpec.js => NoteSpec.js} | 102 ++--- src/static/tests/SpecRunner.html | 4 +- src/static_src/scss/_billboard.scss | 35 +- src/static_src/scss/_dashboard.scss | 25 -- .../scss/{_recognition.scss => _note.scss} | 62 ++- src/static_src/scss/core.scss | 2 +- .../tests/{RecognitionSpec.js => NoteSpec.js} | 102 ++--- ...tion.html => _applet-billboard-notes.html} | 4 +- .../billboard/_partials/_applet-my-posts.html | 17 + .../_partials/_applet-new-post.html} | 6 +- src/templates/apps/billboard/my_notes.html | 49 +++ src/templates/apps/billboard/recognition.html | 49 --- .../dashboard/_partials/_applet-my-notes.html | 17 - .../apps/dashboard/_partials/_form.html | 2 +- src/templates/apps/dashboard/my_notes.html | 20 - src/templates/apps/dashboard/my_posts.html | 20 + .../apps/dashboard/{note.html => post.html} | 22 +- src/templates/apps/dashboard/sky.html | 2 +- 54 files changed, 1373 insertions(+), 1283 deletions(-) create mode 100644 src/apps/applets/migrations/0011_rename_note_applets_to_post.py create mode 100644 src/apps/applets/migrations/0012_rename_recognition_applet_to_notes.py rename src/apps/billboard/static/apps/billboard/{recognition-page.js => note-page.js} (82%) create mode 100644 src/apps/dashboard/migrations/0004_rename_note_to_post.py create mode 100644 src/apps/dashboard/static/apps/dashboard/note.js delete mode 100644 src/apps/dashboard/static/apps/dashboard/recognition.js create mode 100644 src/apps/drama/migrations/0005_rename_recognition_to_note.py rename src/functional_tests/{my_notes_page.py => my_posts_page.py} (90%) rename src/functional_tests/{note_page.py => post_page.py} (62%) create mode 100644 src/functional_tests/test_applet_my_posts.py delete mode 100644 src/functional_tests/test_applet_new_note_item_validation.py rename src/functional_tests/{test_applet_new_note.py => test_applet_new_post.py} (50%) create mode 100644 src/functional_tests/test_applet_new_post_line_validation.py delete mode 100644 src/functional_tests/test_recognition.py rename src/static/tests/{RecognitionSpec.js => NoteSpec.js} (51%) rename src/static_src/scss/{_recognition.scss => _note.scss} (75%) rename src/static_src/tests/{RecognitionSpec.js => NoteSpec.js} (51%) rename src/templates/apps/billboard/_partials/{_applet-billboard-recognition.html => _applet-billboard-notes.html} (54%) create mode 100644 src/templates/apps/billboard/_partials/_applet-my-posts.html rename src/templates/apps/{dashboard/_partials/_applet-new-note.html => billboard/_partials/_applet-new-post.html} (70%) create mode 100644 src/templates/apps/billboard/my_notes.html delete mode 100644 src/templates/apps/billboard/recognition.html delete mode 100644 src/templates/apps/dashboard/_partials/_applet-my-notes.html delete mode 100644 src/templates/apps/dashboard/my_notes.html create mode 100644 src/templates/apps/dashboard/my_posts.html rename src/templates/apps/dashboard/{note.html => post.html} (68%) diff --git a/src/apps/api/serializers.py b/src/apps/api/serializers.py index 70ea59c..ae638ca 100644 --- a/src/apps/api/serializers.py +++ b/src/apps/api/serializers.py @@ -1,30 +1,30 @@ from rest_framework import serializers -from apps.dashboard.models import Item, Note +from apps.dashboard.models import Line, Post from apps.lyric.models import User -class ItemSerializer(serializers.ModelSerializer): +class LineSerializer(serializers.ModelSerializer): text = serializers.CharField() def validate_text(self, value): - note = self.context["note"] - if note.item_set.filter(text=value).exists(): + post = self.context["post"] + if post.lines.filter(text=value).exists(): raise serializers.ValidationError("duplicate") return value class Meta: - model = Item + model = Line fields = ["id", "text"] -class NoteSerializer(serializers.ModelSerializer): +class PostSerializer(serializers.ModelSerializer): name = serializers.ReadOnlyField() url = serializers.CharField(source="get_absolute_url", read_only=True) - items = ItemSerializer(many=True, read_only=True, source="item_set") + lines = LineSerializer(many=True, read_only=True) class Meta: - model = Note - fields = ["id", "name", "url", "items"] + model = Post + fields = ["id", "name", "url", "lines"] class UserSerializer(serializers.ModelSerializer): class Meta: diff --git a/src/apps/api/tests/integrated/test_views.py b/src/apps/api/tests/integrated/test_views.py index 4e8231f..f042ead 100644 --- a/src/apps/api/tests/integrated/test_views.py +++ b/src/apps/api/tests/integrated/test_views.py @@ -1,7 +1,7 @@ from django.test import TestCase from rest_framework.test import APIClient -from apps.dashboard.models import Item, Note +from apps.dashboard.models import Line, Post from apps.lyric.models import User class BaseAPITest(TestCase): @@ -11,76 +11,76 @@ class BaseAPITest(TestCase): self.user = User.objects.create_user("test@example.com") self.client.force_authenticate(user=self.user) -class NoteDetailAPITest(BaseAPITest): - def test_returns_note_with_items(self): - note = Note.objects.create(owner=self.user) - Item.objects.create(text="item 1", note=note) - Item.objects.create(text="item 2", note=note) +class PostDetailAPITest(BaseAPITest): + def test_returns_post_with_lines(self): + post = Post.objects.create(owner=self.user) + Line.objects.create(text="line 1", post=post) + Line.objects.create(text="line 2", post=post) - response = self.client.get(f"/api/notes/{note.id}/") + response = self.client.get(f"/api/posts/{post.id}/") self.assertEqual(response.status_code, 200) - self.assertEqual(response.data["id"], str(note.id)) - self.assertEqual(len(response.data["items"]), 2) + self.assertEqual(response.data["id"], str(post.id)) + self.assertEqual(len(response.data["lines"]), 2) -class NoteItemsAPITest(BaseAPITest): - def test_can_add_item_to_note(self): - note = Note.objects.create(owner=self.user) +class PostLinesAPITest(BaseAPITest): + def test_can_add_line_to_post(self): + post = Post.objects.create(owner=self.user) response = self.client.post( - f"/api/notes/{note.id}/items/", - {"text": "a new item"}, + f"/api/posts/{post.id}/lines/", + {"text": "a new line"}, ) self.assertEqual(response.status_code, 201) - self.assertEqual(Item.objects.count(), 1) - self.assertEqual(Item.objects.first().text, "a new item") + self.assertEqual(Line.objects.count(), 1) + self.assertEqual(Line.objects.first().text, "a new line") - def test_cannot_add_empty_item_to_note(self): - note = Note.objects.create(owner=self.user) + def test_cannot_add_empty_line_to_post(self): + post = Post.objects.create(owner=self.user) response = self.client.post( - f"/api/notes/{note.id}/items/", + f"/api/posts/{post.id}/lines/", {"text": ""}, ) self.assertEqual(response.status_code, 400) - self.assertEqual(Item.objects.count(), 0) + self.assertEqual(Line.objects.count(), 0) - def test_cannot_add_duplicate_item_to_note(self): - note = Note.objects.create(owner=self.user) - Item.objects.create(text="note item", note=note) + def test_cannot_add_duplicate_line_to_post(self): + post = Post.objects.create(owner=self.user) + Line.objects.create(text="post line", post=post) duplicate_response = self.client.post( - f"/api/notes/{note.id}/items/", - {"text": "note item"}, + f"/api/posts/{post.id}/lines/", + {"text": "post line"}, ) self.assertEqual(duplicate_response.status_code, 400) - self.assertEqual(Item.objects.count(), 1) + self.assertEqual(Line.objects.count(), 1) -class NotesAPITest(BaseAPITest): - def test_get_returns_only_users_notes(self): - note1 = Note.objects.create(owner=self.user) - Item.objects.create(text="item 1", note=note1) +class PostsAPITest(BaseAPITest): + def test_get_returns_only_users_posts(self): + post1 = Post.objects.create(owner=self.user) + Line.objects.create(text="line 1", post=post1) other_user = User.objects.create_user("other@example.com") - Note.objects.create(owner=other_user) + Post.objects.create(owner=other_user) - response = self.client.get("/api/notes/") + response = self.client.get("/api/posts/") self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 1) - self.assertEqual(response.data[0]["id"], str(note1.id)) + self.assertEqual(response.data[0]["id"], str(post1.id)) - def test_post_creates_note_with_item(self): + def test_post_creates_post_with_line(self): response = self.client.post( - "/api/notes/", - {"text": "first item"}, + "/api/posts/", + {"text": "first line"}, ) self.assertEqual(response.status_code, 201) - self.assertEqual(Note.objects.count(), 1) - self.assertEqual(Note.objects.first().owner, self.user) - self.assertEqual(Item.objects.first().text, "first item") + self.assertEqual(Post.objects.count(), 1) + self.assertEqual(Post.objects.first().owner, self.user) + self.assertEqual(Line.objects.first().text, "first line") class UserSearchAPITest(BaseAPITest): def test_returns_users_matching_username(self): @@ -98,7 +98,7 @@ class UserSearchAPITest(BaseAPITest): def test_non_searchable_users_are_excluded(self): alice = User.objects.create_user("alice@example.com") alice.username = "princessAli" - alice.save() # searchable defaults to False + alice.save() # searchable defaults to False response = self.client.get("/api/users/?q=prin") diff --git a/src/apps/api/tests/unit/test_serializers.py b/src/apps/api/tests/unit/test_serializers.py index 07379cb..c23cf55 100644 --- a/src/apps/api/tests/unit/test_serializers.py +++ b/src/apps/api/tests/unit/test_serializers.py @@ -1,19 +1,19 @@ from django.test import SimpleTestCase -from apps.api.serializers import ItemSerializer, NoteSerializer +from apps.api.serializers import LineSerializer, PostSerializer -class ItemSerializerTest(SimpleTestCase): +class LineSerializerTest(SimpleTestCase): def test_fields(self): - serializer = ItemSerializer() + serializer = LineSerializer() self.assertEqual( set(serializer.fields.keys()), {"id", "text"}, ) -class NoteSerializerTest(SimpleTestCase): +class PostSerializerTest(SimpleTestCase): def test_fields(self): - serializer = NoteSerializer() + serializer = PostSerializer() self.assertEqual( set(serializer.fields.keys()), - {"id", "name", "url", "items"}, + {"id", "name", "url", "lines"}, ) diff --git a/src/apps/api/urls.py b/src/apps/api/urls.py index 65692ad..a5fcac5 100644 --- a/src/apps/api/urls.py +++ b/src/apps/api/urls.py @@ -4,8 +4,8 @@ from . import views urlpatterns = [ - path('notes/', views.NotesAPI.as_view(), name='api_notes'), - path('notes//', views.NoteDetailAPI.as_view(), name='api_note_detail'), - path('notes//items/', views.NoteItemsAPI.as_view(), name='api_note_items'), + path('posts/', views.PostsAPI.as_view(), name='api_posts'), + path('posts//', views.PostDetailAPI.as_view(), name='api_post_detail'), + path('posts//lines/', views.PostLinesAPI.as_view(), name='api_post_lines'), path('users/', views.UserSearchAPI.as_view(), name='api_users'), ] diff --git a/src/apps/api/views.py b/src/apps/api/views.py index 3abfc29..23239ce 100644 --- a/src/apps/api/views.py +++ b/src/apps/api/views.py @@ -2,36 +2,36 @@ from django.shortcuts import get_object_or_404 from rest_framework.views import APIView from rest_framework.response import Response -from apps.api.serializers import ItemSerializer, NoteSerializer, UserSerializer -from apps.dashboard.models import Item, Note +from apps.api.serializers import LineSerializer, PostSerializer, UserSerializer +from apps.dashboard.models import Line, Post from apps.lyric.models import User -class NoteDetailAPI(APIView): - def get(self, request, note_id): - note = get_object_or_404(Note, id=note_id) - serializer = NoteSerializer(note) +class PostDetailAPI(APIView): + def get(self, request, post_id): + post = get_object_or_404(Post, id=post_id) + serializer = PostSerializer(post) return Response(serializer.data) -class NoteItemsAPI(APIView): - def post(self, request, note_id): - note = get_object_or_404(Note, id=note_id) - serializer = ItemSerializer(data=request.data, context={"note": note}) +class PostLinesAPI(APIView): + def post(self, request, post_id): + post = get_object_or_404(Post, id=post_id) + serializer = LineSerializer(data=request.data, context={"post": post}) if serializer.is_valid(): - serializer.save(note=note) + serializer.save(post=post) return Response(serializer.data, status=201) return Response(serializer.errors, status=400) -class NotesAPI(APIView): +class PostsAPI(APIView): def get(self, request): - notes = Note.objects.filter(owner=request.user) - serializer = NoteSerializer(notes, many=True) + posts = Post.objects.filter(owner=request.user) + serializer = PostSerializer(posts, many=True) return Response(serializer.data) def post(self, request): - note = Note.objects.create(owner=request.user) - item = Item.objects.create(text=request.data.get("text", ""), note=note) - serializer = NoteSerializer(note) + post = Post.objects.create(owner=request.user) + line = Line.objects.create(text=request.data.get("text", ""), post=post) + serializer = PostSerializer(post) return Response(serializer.data, status=201) class UserSearchAPI(APIView): diff --git a/src/apps/applets/migrations/0011_rename_note_applets_to_post.py b/src/apps/applets/migrations/0011_rename_note_applets_to_post.py new file mode 100644 index 0000000..a333f8d --- /dev/null +++ b/src/apps/applets/migrations/0011_rename_note_applets_to_post.py @@ -0,0 +1,24 @@ +from django.db import migrations + + +def rename_note_applets_to_post(apps, schema_editor): + Applet = apps.get_model('applets', 'Applet') + Applet.objects.filter(slug='new-note').update(slug='new-post', name='New Post', context='billboard') + Applet.objects.filter(slug='my-notes').update(slug='my-posts', name='My Posts', context='billboard') + + +def reverse_rename_note_applets_to_post(apps, schema_editor): + Applet = apps.get_model('applets', 'Applet') + Applet.objects.filter(slug='new-post').update(slug='new-note', name='New Note', context='dashboard') + Applet.objects.filter(slug='my-posts').update(slug='my-notes', name='My Notes', context='dashboard') + + +class Migration(migrations.Migration): + + dependencies = [ + ('applets', '0010_recognition_applet'), + ] + + operations = [ + migrations.RunPython(rename_note_applets_to_post, reverse_rename_note_applets_to_post), + ] diff --git a/src/apps/applets/migrations/0012_rename_recognition_applet_to_notes.py b/src/apps/applets/migrations/0012_rename_recognition_applet_to_notes.py new file mode 100644 index 0000000..38a6a92 --- /dev/null +++ b/src/apps/applets/migrations/0012_rename_recognition_applet_to_notes.py @@ -0,0 +1,31 @@ +from django.db import migrations + + +def rename_recognition_applet_to_notes(apps, schema_editor): + Applet = apps.get_model('applets', 'Applet') + Applet.objects.filter(slug='billboard-recognition').update( + slug='billboard-notes', + name='My Notes', + ) + + +def reverse_rename_recognition_applet_to_notes(apps, schema_editor): + Applet = apps.get_model('applets', 'Applet') + Applet.objects.filter(slug='billboard-notes').update( + slug='billboard-recognition', + name='Recognition', + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ('applets', '0011_rename_note_applets_to_post'), + ] + + operations = [ + migrations.RunPython( + rename_recognition_applet_to_notes, + reverse_rename_recognition_applet_to_notes, + ), + ] diff --git a/src/apps/billboard/static/apps/billboard/recognition-page.js b/src/apps/billboard/static/apps/billboard/note-page.js similarity index 82% rename from src/apps/billboard/static/apps/billboard/recognition-page.js rename to src/apps/billboard/static/apps/billboard/note-page.js index 26e0a21..5954be6 100644 --- a/src/apps/billboard/static/apps/billboard/recognition-page.js +++ b/src/apps/billboard/static/apps/billboard/note-page.js @@ -8,7 +8,7 @@ // ── helpers ────────────────────────────────────────────────────────────── function _activeModal() { - return _activeItem && _activeItem.querySelector('.recog-palette-modal'); + return _activeItem && _activeItem.querySelector('.note-palette-modal'); } function _paletteClass(el) { @@ -26,14 +26,14 @@ // Clone from