renamed List to Note everywhere thru-out project in preparation for complete overhaul of applet capabilities

This commit is contained in:
Disco DeDisco
2026-03-11 13:59:43 -04:00
parent aa1cef6e7b
commit f45740d8b3
29 changed files with 435 additions and 394 deletions

View File

@@ -1,6 +1,6 @@
from rest_framework import serializers
from apps.dashboard.models import Item, List
from apps.dashboard.models import Item, Note
from apps.lyric.models import User
@@ -8,8 +8,8 @@ class ItemSerializer(serializers.ModelSerializer):
text = serializers.CharField()
def validate_text(self, value):
list_ = self.context["list"]
if list_.item_set.filter(text=value).exists():
note = self.context["note"]
if note.item_set.filter(text=value).exists():
raise serializers.ValidationError("duplicate")
return value
@@ -17,13 +17,13 @@ class ItemSerializer(serializers.ModelSerializer):
model = Item
fields = ["id", "text"]
class ListSerializer(serializers.ModelSerializer):
class NoteSerializer(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")
class Meta:
model = List
model = Note
fields = ["id", "name", "url", "items"]
class UserSerializer(serializers.ModelSerializer):

View File

@@ -1,7 +1,7 @@
from django.test import TestCase
from rest_framework.test import APIClient
from apps.dashboard.models import Item, List
from apps.dashboard.models import Item, Note
from apps.lyric.models import User
class BaseAPITest(TestCase):
@@ -11,24 +11,24 @@ class BaseAPITest(TestCase):
self.user = User.objects.create_user("test@example.com")
self.client.force_authenticate(user=self.user)
class ListDetailAPITest(BaseAPITest):
def test_returns_list_with_items(self):
list_ = List.objects.create(owner=self.user)
Item.objects.create(text="item 1", list=list_)
Item.objects.create(text="item 2", list=list_)
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)
response = self.client.get(f"/api/lists/{list_.id}/")
response = self.client.get(f"/api/notes/{note.id}/")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["id"], str(list_.id))
self.assertEqual(response.data["id"], str(note.id))
self.assertEqual(len(response.data["items"]), 2)
class ListItemsAPITest(BaseAPITest):
def test_can_add_item_to_list(self):
list_ = List.objects.create(owner=self.user)
class NoteItemsAPITest(BaseAPITest):
def test_can_add_item_to_note(self):
note = Note.objects.create(owner=self.user)
response = self.client.post(
f"/api/lists/{list_.id}/items/",
f"/api/notes/{note.id}/items/",
{"text": "a new item"},
)
@@ -36,50 +36,50 @@ class ListItemsAPITest(BaseAPITest):
self.assertEqual(Item.objects.count(), 1)
self.assertEqual(Item.objects.first().text, "a new item")
def test_cannot_add_empty_item_to_list(self):
list_ = List.objects.create(owner=self.user)
def test_cannot_add_empty_item_to_note(self):
note = Note.objects.create(owner=self.user)
response = self.client.post(
f"/api/lists/{list_.id}/items/",
f"/api/notes/{note.id}/items/",
{"text": ""},
)
self.assertEqual(response.status_code, 400)
self.assertEqual(Item.objects.count(), 0)
def test_cannot_add_duplicate_item_to_list(self):
list_ = List.objects.create(owner=self.user)
Item.objects.create(text="list item", list=list_)
def test_cannot_add_duplicate_item_to_note(self):
note = Note.objects.create(owner=self.user)
Item.objects.create(text="note item", note=note)
duplicate_response = self.client.post(
f"/api/lists/{list_.id}/items/",
{"text": "list item"},
f"/api/notes/{note.id}/items/",
{"text": "note item"},
)
self.assertEqual(duplicate_response.status_code, 400)
self.assertEqual(Item.objects.count(), 1)
class ListsAPITest(BaseAPITest):
def test_get_returns_only_users_lists(self):
list1 = List.objects.create(owner=self.user)
Item.objects.create(text="item 1", list=list1)
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)
other_user = User.objects.create_user("other@example.com")
List.objects.create(owner=other_user)
Note.objects.create(owner=other_user)
response = self.client.get("/api/lists/")
response = self.client.get("/api/notes/")
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 1)
self.assertEqual(response.data[0]["id"], str(list1.id))
self.assertEqual(response.data[0]["id"], str(note1.id))
def test_post_creates_list_with_item(self):
def test_post_creates_note_with_item(self):
response = self.client.post(
"/api/lists/",
"/api/notes/",
{"text": "first item"},
)
self.assertEqual(response.status_code, 201)
self.assertEqual(List.objects.count(), 1)
self.assertEqual(List.objects.first().owner, self.user)
self.assertEqual(Note.objects.count(), 1)
self.assertEqual(Note.objects.first().owner, self.user)
self.assertEqual(Item.objects.first().text, "first item")
class UserSearchAPITest(BaseAPITest):

View File

@@ -1,5 +1,5 @@
from django.test import SimpleTestCase
from apps.api.serializers import ItemSerializer, ListSerializer
from apps.api.serializers import ItemSerializer, NoteSerializer
class ItemSerializerTest(SimpleTestCase):
@@ -10,11 +10,10 @@ class ItemSerializerTest(SimpleTestCase):
{"id", "text"},
)
class ListSerializerTest(SimpleTestCase):
class NoteSerializerTest(SimpleTestCase):
def test_fields(self):
serializer = ListSerializer()
serializer = NoteSerializer()
self.assertEqual(
set(serializer.fields.keys()),
{"id", "name", "url", "items"},
)

View File

@@ -4,9 +4,8 @@ from . import views
urlpatterns = [
path('lists/', views.ListsAPI.as_view(), name='api_lists'),
path('lists/<uuid:list_id>/', views.ListDetailAPI.as_view(), name='api_list_detail'),
path('lists/<uuid:list_id>/items/', views.ListItemsAPI.as_view(), name='api_list_items'),
path('notes/', views.NotesAPI.as_view(), name='api_notes'),
path('notes/<uuid:note_id>/', views.NoteDetailAPI.as_view(), name='api_note_detail'),
path('notes/<uuid:note_id>/items/', views.NoteItemsAPI.as_view(), name='api_note_items'),
path('users/', views.UserSearchAPI.as_view(), name='api_users'),
]

View File

@@ -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, ListSerializer, UserSerializer
from apps.dashboard.models import Item, List
from apps.api.serializers import ItemSerializer, NoteSerializer, UserSerializer
from apps.dashboard.models import Item, Note
from apps.lyric.models import User
class ListDetailAPI(APIView):
def get(self, request, list_id):
list_ = get_object_or_404(List, id=list_id)
serializer = ListSerializer(list_)
class NoteDetailAPI(APIView):
def get(self, request, note_id):
note = get_object_or_404(Note, id=note_id)
serializer = NoteSerializer(note)
return Response(serializer.data)
class ListItemsAPI(APIView):
def post(self, request, list_id):
list_ = get_object_or_404(List, id=list_id)
serializer = ItemSerializer(data=request.data, context={"list": list_})
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})
if serializer.is_valid():
serializer.save(list=list_)
serializer.save(note=note)
return Response(serializer.data, status=201)
return Response(serializer.errors, status=400)
class ListsAPI(APIView):
class NotesAPI(APIView):
def get(self, request):
lists = List.objects.filter(owner=request.user)
serializer = ListSerializer(lists, many=True)
notes = Note.objects.filter(owner=request.user)
serializer = NoteSerializer(notes, many=True)
return Response(serializer.data)
def post(self, request):
list_ = List.objects.create(owner=request.user)
item = Item.objects.create(text=request.data.get("text", ""), list=list_)
serializer = ListSerializer(list_)
note = Note.objects.create(owner=request.user)
item = Item.objects.create(text=request.data.get("text", ""), note=note)
serializer = NoteSerializer(note)
return Response(serializer.data, status=201)
class UserSearchAPI(APIView):

View File

@@ -0,0 +1,24 @@
from django.db import migrations
def rename_list_slugs(apps, schema_editor):
Applet = apps.get_model('applets', 'Applet')
Applet.objects.filter(slug='new-list').update(slug='new-note', name='New Note')
Applet.objects.filter(slug='my-lists').update(slug='my-notes', name='My Notes')
def reverse_rename_list_slugs(apps, schema_editor):
Applet = apps.get_model('applets', 'Applet')
Applet.objects.filter(slug='new-note').update(slug='new-list', name='New List')
Applet.objects.filter(slug='my-notes').update(slug='my-lists', name='My Lists')
class Migration(migrations.Migration):
dependencies = [
('applets', '0003_wallet_applets'),
]
operations = [
migrations.RunPython(rename_list_slugs, reverse_rename_list_slugs),
]

View File

@@ -2,8 +2,8 @@ from django import forms
from django.core.exceptions import ValidationError
from .models import Item
DUPLICATE_ITEM_ERROR = "You've already logged this to your list"
EMPTY_ITEM_ERROR = "You can't have an empty list item"
DUPLICATE_ITEM_ERROR = "You've already logged this to your note"
EMPTY_ITEM_ERROR = "You can't have an empty note item"
class ItemForm(forms.Form):
text = forms.CharField(
@@ -11,22 +11,22 @@ class ItemForm(forms.Form):
required=True,
)
def save(self, for_list):
def save(self, for_note):
return Item.objects.create(
list=for_list,
note=for_note,
text=self.cleaned_data["text"],
)
class ExistingListItemForm(ItemForm):
def __init__(self, for_list, *args, **kwargs):
class ExistingNoteItemForm(ItemForm):
def __init__(self, for_note, *args, **kwargs):
super().__init__(*args, **kwargs)
self._for_list = for_list
self._for_note = for_note
def clean_text(self):
text = self.cleaned_data["text"]
if self._for_list.item_set.filter(text=text).exists():
if self._for_note.item_set.filter(text=text).exists():
raise forms.ValidationError(DUPLICATE_ITEM_ERROR)
return text
def save(self):
return super().save(for_list=self._for_list)
return super().save(for_note=self._for_note)

View File

@@ -0,0 +1,20 @@
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0001_initial'),
]
operations = [
migrations.RenameModel(
old_name='List',
new_name='Note',
),
migrations.RenameField(
model_name='Item',
old_name='list',
new_name='note',
),
]

View File

@@ -4,11 +4,11 @@ from django.db import models
from django.urls import reverse
class List(models.Model):
class Note(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
owner = models.ForeignKey(
"lyric.User",
related_name="lists",
related_name="notes",
blank=True,
null=True,
on_delete=models.CASCADE,
@@ -16,7 +16,7 @@ class List(models.Model):
shared_with = models.ManyToManyField(
"lyric.User",
related_name="shared_lists",
related_name="shared_notes",
blank=True,
)
@@ -25,15 +25,15 @@ class List(models.Model):
return self.item_set.first().text
def get_absolute_url(self):
return reverse("view_list", args=[self.id])
return reverse("view_note", args=[self.id])
class Item(models.Model):
text = models.TextField(default="")
list = models.ForeignKey(List, default=None, on_delete=models.CASCADE)
note = models.ForeignKey(Note, default=None, on_delete=models.CASCADE)
class Meta:
ordering = ("id",)
unique_together = ("list", "text")
unique_together = ("note", "text")
def __str__(self):
return self.text

View File

@@ -3,39 +3,39 @@ from django.test import TestCase
from apps.dashboard.forms import (
DUPLICATE_ITEM_ERROR,
EMPTY_ITEM_ERROR,
ExistingListItemForm,
ExistingNoteItemForm,
ItemForm,
)
from apps.dashboard.models import Item, List
from apps.dashboard.models import Item, Note
class ItemFormTest(TestCase):
def test_form_save_handles_saving_to_a_list(self):
mylist = List.objects.create()
def test_form_save_handles_saving_to_a_note(self):
mynote = Note.objects.create()
form = ItemForm(data={"text": "do re mi"})
self.assertTrue(form.is_valid())
new_item = form.save(for_list=mylist)
new_item = form.save(for_note=mynote)
self.assertEqual(new_item, Item.objects.get())
self.assertEqual(new_item.text, "do re mi")
self.assertEqual(new_item.list, mylist)
self.assertEqual(new_item.note, mynote)
class ExistingListItemFormTest(TestCase):
class ExistingNoteItemFormTest(TestCase):
def test_form_validation_for_blank_items(self):
list_ = List.objects.create()
form = ExistingListItemForm(for_list=list_, data={"text": ""})
note = Note.objects.create()
form = ExistingNoteItemForm(for_note=note, data={"text": ""})
self.assertFalse(form.is_valid())
self.assertEqual(form.errors["text"], [EMPTY_ITEM_ERROR])
def test_form_validation_for_duplicate_items(self):
list_ = List.objects.create()
Item.objects.create(list=list_, text="twins, basil")
form = ExistingListItemForm(for_list=list_, data={"text": "twins, basil"})
note = Note.objects.create()
Item.objects.create(note=note, text="twins, basil")
form = ExistingNoteItemForm(for_note=note, data={"text": "twins, basil"})
self.assertFalse(form.is_valid())
self.assertEqual(form.errors["text"], [DUPLICATE_ITEM_ERROR])
def test_form_save(self):
mylist = List.objects.create()
form = ExistingListItemForm(for_list=mylist, data={"text": "howdy"})
mynote = Note.objects.create()
form = ExistingNoteItemForm(for_note=mynote, data={"text": "howdy"})
self.assertTrue(form.is_valid())
new_item = form.save()
self.assertEqual(new_item, Item.objects.get())

View File

@@ -2,69 +2,69 @@ from django.core.exceptions import ValidationError
from django.db.utils import IntegrityError
from django.test import TestCase
from apps.dashboard.models import Item, List
from apps.dashboard.models import Item, Note
from apps.lyric.models import User
class ItemModelTest(TestCase):
def test_item_is_related_to_list(self):
mylist = List.objects.create()
def test_item_is_related_to_note(self):
mynote = Note.objects.create()
item = Item()
item.list = mylist
item.note = mynote
item.save()
self.assertIn(item, mylist.item_set.all())
self.assertIn(item, mynote.item_set.all())
def test_cannot_save_null_list_items(self):
mylist = List.objects.create()
item = Item(list=mylist, text=None)
def test_cannot_save_null_note_items(self):
mynote = Note.objects.create()
item = Item(note=mynote, text=None)
with self.assertRaises(IntegrityError):
item.save()
def test_cannot_save_empty_list_items(self):
mylist = List.objects.create()
item = Item(list=mylist, text="")
def test_cannot_save_empty_note_items(self):
mynote = Note.objects.create()
item = Item(note=mynote, text="")
with self.assertRaises(ValidationError):
item.full_clean()
def test_duplicate_items_are_invalid(self):
mylist = List.objects.create()
Item.objects.create(list=mylist, text="jklol")
mynote = Note.objects.create()
Item.objects.create(note=mynote, text="jklol")
with self.assertRaises(ValidationError):
item = Item(list=mylist, text="jklol")
item = Item(note=mynote, text="jklol")
item.full_clean()
def test_still_can_save_same_item_to_different_lists(self):
list1 = List.objects.create()
list2 = List.objects.create()
Item.objects.create(list=list1, text="nojk")
item = Item(list=list2, text="nojk")
def test_still_can_save_same_item_to_different_notes(self):
note1 = Note.objects.create()
note2 = Note.objects.create()
Item.objects.create(note=note1, text="nojk")
item = Item(note=note2, text="nojk")
item.full_clean() # should not raise
class ListModelTest(TestCase):
class NoteModelTest(TestCase):
def test_get_absolute_url(self):
mylist = List.objects.create()
self.assertEqual(mylist.get_absolute_url(), f"/dashboard/list/{mylist.id}/")
mynote = Note.objects.create()
self.assertEqual(mynote.get_absolute_url(), f"/dashboard/note/{mynote.id}/")
def test_list_items_order(self):
list1 = List.objects.create()
item1 = Item.objects.create(list=list1, text="i1")
item2 = Item.objects.create(list=list1, text="item 2")
item3 = Item.objects.create(list=list1, text="3")
def test_note_items_order(self):
note1 = Note.objects.create()
item1 = Item.objects.create(note=note1, text="i1")
item2 = Item.objects.create(note=note1, text="item 2")
item3 = Item.objects.create(note=note1, text="3")
self.assertEqual(
list(list1.item_set.all()),
list(note1.item_set.all()),
[item1, item2, item3],
)
def test_lists_can_have_owners(self):
def test_notes_can_have_owners(self):
user = User.objects.create(email="a@b.cde")
mylist = List.objects.create(owner=user)
self.assertIn(mylist, user.lists.all())
mynote = Note.objects.create(owner=user)
self.assertIn(mynote, user.notes.all())
def test_list_owner_is_optional(self):
List.objects.create()
def test_note_owner_is_optional(self):
Note.objects.create()
def test_list_name_is_first_item_text(self):
list_ = List.objects.create()
Item.objects.create(list=list_, text="first item")
Item.objects.create(list=list_, text="second item")
self.assertEqual(list_.name, "first item")
def test_note_name_is_first_item_text(self):
note = Note.objects.create()
Item.objects.create(note=note, text="first item")
Item.objects.create(note=note, text="second item")
self.assertEqual(note.name, "first item")

View File

@@ -10,7 +10,7 @@ from apps.dashboard.forms import (
DUPLICATE_ITEM_ERROR,
EMPTY_ITEM_ERROR,
)
from apps.dashboard.models import Item, List
from apps.dashboard.models import Item, Note
from apps.lyric.models import User
@@ -18,7 +18,7 @@ class HomePageTest(TestCase):
def setUp(self):
self.user = User.objects.create(email="disco@test.io")
self.client.force_login(self.user)
Applet.objects.get_or_create(slug="new-list", defaults={"name": "New List"})
Applet.objects.get_or_create(slug="new-note", defaults={"name": "New Note"})
def test_uses_home_template(self):
response = self.client.get('/')
@@ -28,36 +28,36 @@ class HomePageTest(TestCase):
response = self.client.get('/')
parsed = lxml.html.fromstring(response.content)
forms = parsed.cssselect('form[method=POST]')
self.assertIn("/dashboard/new_list", [form.get("action") for form in forms])
[form] = [form for form in forms if form.get("action") == "/dashboard/new_list"]
self.assertIn("/dashboard/new_note", [form.get("action") for form in forms])
[form] = [form for form in forms if form.get("action") == "/dashboard/new_note"]
inputs = form.cssselect("input")
self.assertIn("text", [input.get("name") for input in inputs])
class NewListTest(TestCase):
class NewNoteTest(TestCase):
def setUp(self):
user = User.objects.create(email="disco@test.io")
self.client.force_login(user)
def test_can_save_a_POST_request(self):
self.client.post("/dashboard/new_list", data={"text": "A new list item"})
self.client.post("/dashboard/new_note", data={"text": "A new note item"})
self.assertEqual(Item.objects.count(), 1)
new_item = Item.objects.get()
self.assertEqual(new_item.text, "A new list item")
self.assertEqual(new_item.text, "A new note item")
def test_redirects_after_POST(self):
response = self.client.post("/dashboard/new_list", data={"text": "A new list item"})
new_list = List.objects.get()
self.assertRedirects(response, f"/dashboard/list/{new_list.id}/")
response = self.client.post("/dashboard/new_note", data={"text": "A new note item"})
new_note = Note.objects.get()
self.assertRedirects(response, f"/dashboard/note/{new_note.id}/")
# Post invalid input helper
def post_invalid_input(self):
return self.client.post("/dashboard/new_list", data={"text": ""})
return self.client.post("/dashboard/new_note", data={"text": ""})
def test_for_invalid_input_nothing_saved_to_db(self):
self.post_invalid_input()
self.assertEqual(Item.objects.count(), 0)
def test_for_invalid_input_renders_list_template(self):
def test_for_invalid_input_renders_home_template(self):
response = self.post_invalid_input()
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "apps/dashboard/home.html")
@@ -67,15 +67,15 @@ class NewListTest(TestCase):
self.assertContains(response, html.escape(EMPTY_ITEM_ERROR))
@override_settings(COMPRESS_ENABLED=False)
class ListViewTest(TestCase):
def test_uses_list_template(self):
mylist = List.objects.create()
response = self.client.get(f"/dashboard/list/{mylist.id}/")
self.assertTemplateUsed(response, "apps/dashboard/list.html")
class NoteViewTest(TestCase):
def test_uses_note_template(self):
mynote = Note.objects.create()
response = self.client.get(f"/dashboard/note/{mynote.id}/")
self.assertTemplateUsed(response, "apps/dashboard/note.html")
def test_renders_input_form(self):
mylist = List.objects.create()
url = f"/dashboard/list/{mylist.id}/"
mynote = Note.objects.create()
url = f"/dashboard/note/{mynote.id}/"
response = self.client.get(url)
parsed = lxml.html.fromstring(response.content)
forms = parsed.cssselect("form[method=POST]")
@@ -84,58 +84,58 @@ class ListViewTest(TestCase):
inputs = form.cssselect("input")
self.assertIn("text", [input.get("name") for input in inputs])
def test_displays_only_items_for_that_list(self):
def test_displays_only_items_for_that_note(self):
# Given/Arrange
correct_list = List.objects.create()
Item.objects.create(text="itemey 1", list=correct_list)
Item.objects.create(text="itemey 2", list=correct_list)
other_list = List.objects.create()
Item.objects.create(text="other list item", list=other_list)
correct_note = Note.objects.create()
Item.objects.create(text="itemey 1", note=correct_note)
Item.objects.create(text="itemey 2", note=correct_note)
other_note = Note.objects.create()
Item.objects.create(text="other note item", note=other_note)
# When/Act
response = self.client.get(f"/dashboard/list/{correct_list.id}/")
response = self.client.get(f"/dashboard/note/{correct_note.id}/")
# Then/Assert
self.assertContains(response, "itemey 1")
self.assertContains(response, "itemey 2")
self.assertNotContains(response, "other list item")
self.assertNotContains(response, "other note item")
def test_can_save_a_POST_request_to_an_existing_list(self):
other_list = List.objects.create()
correct_list = List.objects.create()
def test_can_save_a_POST_request_to_an_existing_note(self):
other_note = Note.objects.create()
correct_note = Note.objects.create()
self.client.post(
f"/dashboard/list/{correct_list.id}/",
data={"text": "A new item for an existing list"},
f"/dashboard/note/{correct_note.id}/",
data={"text": "A new item for an existing note"},
)
self.assertEqual(Item.objects.count(), 1)
new_item = Item.objects.get()
self.assertEqual(new_item.text, "A new item for an existing list")
self.assertEqual(new_item.list, correct_list)
self.assertEqual(new_item.text, "A new item for an existing note")
self.assertEqual(new_item.note, correct_note)
def test_POST_redirects_to_list_view(self):
other_list = List.objects.create()
correct_list = List.objects.create()
def test_POST_redirects_to_note_view(self):
other_note = Note.objects.create()
correct_note = Note.objects.create()
response = self.client.post(
f"/dashboard/list/{correct_list.id}/",
data={"text": "A new item for an existing list"},
f"/dashboard/note/{correct_note.id}/",
data={"text": "A new item for an existing note"},
)
self.assertRedirects(response, f"/dashboard/list/{correct_list.id}/")
self.assertRedirects(response, f"/dashboard/note/{correct_note.id}/")
# Post invalid input helper
def post_invalid_input(self):
mylist = List.objects.create()
return self.client.post(f"/dashboard/list/{mylist.id}/", data={"text": ""})
mynote = Note.objects.create()
return self.client.post(f"/dashboard/note/{mynote.id}/", data={"text": ""})
def test_for_invalid_input_nothing_saved_to_db(self):
self.post_invalid_input()
self.assertEqual(Item.objects.count(), 0)
def test_for_invalid_input_renders_list_template(self):
def test_for_invalid_input_renders_note_template(self):
response = self.post_invalid_input()
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "apps/dashboard/list.html")
self.assertTemplateUsed(response, "apps/dashboard/note.html")
def test_for_invalid_input_shows_error_on_page(self):
response = self.post_invalid_input()
@@ -147,26 +147,26 @@ class ListViewTest(TestCase):
[input] = parsed.cssselect("input[name=text]")
self.assertIn("is-invalid", set(input.classes))
def test_duplicate_item_validation_errors_end_up_on_lists_page(self):
list1 = List.objects.create()
Item.objects.create(list=list1, text="lorem ipsum")
def test_duplicate_item_validation_errors_end_up_on_note_page(self):
note1 = Note.objects.create()
Item.objects.create(note=note1, text="lorem ipsum")
response = self.client.post(
f"/dashboard/list/{list1.id}/",
f"/dashboard/note/{note1.id}/",
data={"text": "lorem ipsum"},
)
expected_error = html.escape(DUPLICATE_ITEM_ERROR)
self.assertContains(response, expected_error)
self.assertTemplateUsed(response, "apps/dashboard/list.html")
self.assertTemplateUsed(response, "apps/dashboard/note.html")
self.assertEqual(Item.objects.all().count(), 1)
class MyListsTest(TestCase):
def test_my_lists_url_renders_my_lists_template(self):
class MyNotesTest(TestCase):
def test_my_notes_url_renders_my_notes_template(self):
user = User.objects.create(email="a@b.cde")
self.client.force_login(user)
response = self.client.get(f"/dashboard/users/{user.id}/")
self.assertTemplateUsed(response, "apps/dashboard/my_lists.html")
self.assertTemplateUsed(response, "apps/dashboard/my_notes.html")
def test_passes_correct_owner_to_template(self):
User.objects.create(email="wrongowner@example.com")
@@ -175,20 +175,20 @@ class MyListsTest(TestCase):
response = self.client.get(f"/dashboard/users/{correct_user.id}/")
self.assertEqual(response.context["owner"], correct_user)
def test_list_owner_is_saved_if_user_is_authenticated(self):
def test_note_owner_is_saved_if_user_is_authenticated(self):
user = User.objects.create(email="a@b.cde")
self.client.force_login(user)
self.client.post("/dashboard/new_list", data={"text": "new item"})
new_list = List.objects.get()
self.assertEqual(new_list.owner, user)
self.client.post("/dashboard/new_note", data={"text": "new item"})
new_note = Note.objects.get()
self.assertEqual(new_note.owner, user)
def test_my_lists_redirects_if_not_logged_in(self):
def test_my_notes_redirects_if_not_logged_in(self):
user = User.objects.create(email="a@b.cde")
response = self.client.get(f"/dashboard/users/{user.id}/")
self.assertRedirects(response, "/")
def test_my_lists_returns_403_for_wrong_user(self):
# create two users, login as user_a, request user_b's my_lists url
def test_my_notes_returns_403_for_wrong_user(self):
# create two users, login as user_a, request user_b's my_notes url
user1 = User.objects.create(email="a@b.cde")
user2 = User.objects.create(email="wrongowner@example.com")
self.client.force_login(user2)
@@ -196,50 +196,50 @@ class MyListsTest(TestCase):
# assert 403
self.assertEqual(response.status_code, 403)
class ShareListTest(TestCase):
def test_post_to_share_list_url_redirects_to_list(self):
our_list = List.objects.create()
class ShareNoteTest(TestCase):
def test_post_to_share_note_url_redirects_to_note(self):
our_note = Note.objects.create()
alice = User.objects.create(email="alice@example.com")
response = self.client.post(
f"/dashboard/list/{our_list.id}/share_list",
f"/dashboard/note/{our_note.id}/share_note",
data={"recipient": "alice@example.com"},
)
self.assertRedirects(response, f"/dashboard/list/{our_list.id}/")
self.assertRedirects(response, f"/dashboard/note/{our_note.id}/")
def test_post_with_email_adds_user_to_shared_with(self):
our_list = List.objects.create()
our_note = Note.objects.create()
alice = User.objects.create(email="alice@example.com")
self.client.post(
f"/dashboard/list/{our_list.id}/share_list",
f"/dashboard/note/{our_note.id}/share_note",
data={"recipient": "alice@example.com"},
)
self.assertIn(alice, our_list.shared_with.all())
self.assertIn(alice, our_note.shared_with.all())
def test_post_with_nonexistent_email_redirects_to_list(self):
our_list = List.objects.create()
def test_post_with_nonexistent_email_redirects_to_note(self):
our_note = Note.objects.create()
response = self.client.post(
f"/dashboard/list/{our_list.id}/share_list",
f"/dashboard/note/{our_note.id}/share_note",
data={"recipient": "nobody@example.com"},
)
self.assertRedirects(
response,
f"/dashboard/list/{our_list.id}/",
f"/dashboard/note/{our_note.id}/",
fetch_redirect_response=False,
)
def test_share_list_does_not_add_owner_as_recipient(self):
def test_share_note_does_not_add_owner_as_recipient(self):
owner = User.objects.create(email="owner@example.com")
our_list = List.objects.create(owner=owner)
our_note = Note.objects.create(owner=owner)
self.client.force_login(owner)
self.client.post(reverse("share_list", args=[our_list.id]),
self.client.post(reverse("share_note", args=[our_note.id]),
data={"recipient": "owner@example.com"})
self.assertNotIn(owner, our_list.shared_with.all())
self.assertNotIn(owner, our_note.shared_with.all())
@override_settings(MESSAGE_STORAGE='django.contrib.messages.storage.session.SessionStorage')
def test_share_list_shows_privacy_safe_message(self):
our_list = List.objects.create()
def test_share_note_shows_privacy_safe_message(self):
our_note = Note.objects.create()
response = self.client.post(
f"/dashboard/list/{our_list.id}/share_list",
f"/dashboard/note/{our_note.id}/share_note",
data={"recipient": "nobody@example.com"},
follow=True,
)
@@ -249,26 +249,26 @@ class ShareListTest(TestCase):
"An invite has been sent if that address is registered.",
)
class ViewAuthListTest(TestCase):
class ViewAuthNoteTest(TestCase):
def setUp(self):
self.owner = User.objects.create(email="disco@example.com")
self.our_list = List.objects.create(owner=self.owner)
self.our_note = Note.objects.create(owner=self.owner)
def test_anonymous_user_is_redirected(self):
response = self.client.get(reverse("view_list", args=[self.our_list.id]))
response = self.client.get(reverse("view_note", args=[self.our_note.id]))
self.assertRedirects(response, "/", fetch_redirect_response=False)
def test_non_owner_non_shared_user_gets_403(self):
stranger = User.objects.create(email="stranger@example.com")
self.client.force_login(stranger)
response = self.client.get(reverse("view_list", args=[self.our_list.id]))
response = self.client.get(reverse("view_note", args=[self.our_note.id]))
self.assertEqual(response.status_code, 403)
def test_shared_with_user_can_access_list(self):
def test_shared_with_user_can_access_note(self):
guest = User.objects.create(email="guest@example.com")
self.our_list.shared_with.add(guest)
self.our_note.shared_with.add(guest)
self.client.force_login(guest)
response = self.client.get(reverse("view_list", args=[self.our_list.id]))
response = self.client.get(reverse("view_note", args=[self.our_note.id]))
self.assertEqual(response.status_code, 200)
@override_settings(COMPRESS_ENABLED=False)
@@ -299,7 +299,7 @@ class SetPaletteTest(TestCase):
response = self.client.post("/dashboard/set_palette", data={"palette": "palette-default"})
self.assertRedirects(response, "/", fetch_redirect_response=False)
def test_my_lists_contains_set_palette_form(self):
def test_dashboard_contains_set_palette_form(self):
response = self.client.get(self.url)
parsed = lxml.html.fromstring(response.content)
forms = parsed.cssselect('form[action="/dashboard/set_palette"]')

View File

@@ -2,12 +2,12 @@ from django.urls import path
from . import views
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('new_note', views.new_note, name='new_note'),
path('note/<uuid:note_id>/', views.view_note, name='view_note'),
path('note/<uuid:note_id>/share_note', views.share_note, name="share_note"),
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('users/<uuid:user_id>/', views.my_notes, name='my_notes'),
path('toggle_applets', views.toggle_applets, name="toggle_applets"),
path('wallet/', views.wallet, name='wallet'),
path('wallet/toggle-applets', views.toggle_wallet_applets, name='toggle_wallet_applets'),

View File

@@ -10,12 +10,12 @@ from django.views.decorators.csrf import ensure_csrf_cookie
from apps.applets.models import Applet, UserApplet
from apps.applets.utils import applet_context
from apps.dashboard.forms import ExistingListItemForm, ItemForm
from apps.dashboard.models import Item, List
from apps.dashboard.forms import ExistingNoteItemForm, ItemForm
from apps.dashboard.models import Item, Note
from apps.lyric.models import PaymentMethod, Token, User, Wallet
APPLET_ORDER = ["wallet", "new-list", "my-lists", "username", "palette"]
APPLET_ORDER = ["wallet", "new-note", "my-notes", "username", "palette"]
UNLOCKED_PALETTES = frozenset(["palette-default"])
PALETTES = [
{"name": "palette-default", "label": "Earthman", "locked": False},
@@ -27,9 +27,9 @@ PALETTES = [
]
def _recent_lists(user, limit=3):
def _recent_notes(user, limit=3):
return (
List
Note
.objects
.filter(Q(owner=user) | Q(shared_with=user))
.annotate(last_item=Max('item__id'))
@@ -45,18 +45,18 @@ def home_page(request):
}
if request.user.is_authenticated:
context["applets"] = applet_context(request.user, "dashboard")
context["recent_lists"] = _recent_lists(request.user)
context["recent_notes"] = _recent_notes(request.user)
return render(request, "apps/dashboard/home.html", context)
def new_list(request):
def new_note(request):
form = ItemForm(data=request.POST)
if form.is_valid():
nulist = List.objects.create()
nunote = Note.objects.create()
if request.user.is_authenticated:
nulist.owner = request.user
nulist.save()
form.save(for_list=nulist)
return redirect(nulist)
nunote.owner = request.user
nunote.save()
form.save(for_note=nunote)
return redirect(nunote)
else:
context = {
"form": form,
@@ -65,46 +65,46 @@ def new_list(request):
}
if request.user.is_authenticated:
context["applets"] = applet_context(request.user, "dashboard")
context["recent_lists"] = _recent_lists(request.user)
context["recent_notes"] = _recent_notes(request.user)
return render(request, "apps/dashboard/home.html", context)
def view_list(request, list_id):
our_list = List.objects.get(id=list_id)
def view_note(request, note_id):
our_note = Note.objects.get(id=note_id)
if our_list.owner:
if our_note.owner:
if not request.user.is_authenticated:
return redirect("/")
if request.user != our_list.owner and request.user not in our_list.shared_with.all():
if request.user != our_note.owner and request.user not in our_note.shared_with.all():
return HttpResponseForbidden()
form = ExistingListItemForm(for_list=our_list)
form = ExistingNoteItemForm(for_note=our_note)
if request.method == "POST":
form = ExistingListItemForm(for_list=our_list, data=request.POST)
form = ExistingNoteItemForm(for_note=our_note, data=request.POST)
if form.is_valid():
form.save()
return redirect(our_list)
return render(request, "apps/dashboard/list.html", {"list": our_list, "form": form})
return redirect(our_note)
return render(request, "apps/dashboard/note.html", {"note": our_note, "form": form})
def my_lists(request, user_id):
def my_notes(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_lists.html", {"owner": owner})
return render(request, "apps/dashboard/my_notes.html", {"owner": owner})
def share_list(request, list_id):
our_list = List.objects.get(id=list_id)
def share_note(request, note_id):
our_note = Note.objects.get(id=note_id)
try:
recipient = User.objects.get(email=request.POST["recipient"])
if recipient == request.user:
return redirect(our_list)
our_list.shared_with.add(recipient)
return redirect(our_note)
our_note.shared_with.add(recipient)
except User.DoesNotExist:
pass
messages.success(request, "An invite has been sent if that address is registered.")
return redirect(our_list)
return redirect(our_note)
@login_required(login_url="/")
def set_palette(request):
@@ -137,7 +137,7 @@ def toggle_applets(request):
"applets": applet_context(request.user, "dashboard"),
"palettes": PALETTES,
"form": ItemForm(),
"recent_lists": _recent_lists(request.user),
"recent_notes": _recent_notes(request.user),
})
return redirect("home")

View File

@@ -47,7 +47,7 @@ class FunctionalTest(StaticLiveServerTestCase):
if self.test_server:
self.live_server_url = 'http://' + self.test_server
reset_database(self.test_server)
Applet.objects.get_or_create(slug="new-list", defaults={"name": "New List"})
Applet.objects.get_or_create(slug="new-note", defaults={"name": "New Note"})
def tearDown(self):
if self._test_has_failed():

View File

@@ -3,11 +3,11 @@ from selenium.webdriver.common.by import By
from apps.lyric.models import User
class MyListsPage:
class MyNotesPage:
def __init__(self, test):
self.test = test
def go_to_my_lists_page(self, email):
def go_to_my_notes_page(self, email):
self.test.browser.get(self.test.live_server_url)
user = User.objects.get(email=email)
self.test.browser.get(

View File

@@ -4,15 +4,15 @@ from selenium.webdriver.common.keys import Keys
from .base import wait
class ListPage:
class NotePage:
def __init__(self, test):
self.test = test
def get_table_rows(self):
return self.test.browser.find_elements(By.CSS_SELECTOR, "#id_list_table tr")
return self.test.browser.find_elements(By.CSS_SELECTOR, "#id_note_table tr")
@wait
def wait_for_row_in_list_table(self, item_text, item_number):
def wait_for_row_in_note_table(self, item_text, item_number):
expected_row_text = f"{item_number}. {item_text}"
rows = self.get_table_rows()
self.test.assertIn(expected_row_text, [row.text for row in rows])
@@ -20,11 +20,11 @@ class ListPage:
def get_item_input_box(self):
return self.test.browser.find_element(By.ID, "id_text")
def add_list_item(self, item_text):
def add_note_item(self, item_text):
new_item_no = len(self.get_table_rows()) + 1
self.get_item_input_box().send_keys(item_text)
self.get_item_input_box().send_keys(Keys.ENTER)
self.wait_for_row_in_list_table(item_text, new_item_no)
self.wait_for_row_in_note_table(item_text, new_item_no)
return self
def get_share_box(self):
@@ -36,10 +36,10 @@ class ListPage:
def get_shared_with_list(self):
return self.test.browser.find_elements(
By.CSS_SELECTOR,
".list-recipient"
".note-recipient"
)
def share_list_with(self, email):
def share_note_with(self, email):
self.get_share_box().send_keys(email)
self.get_share_box().send_keys(Keys.ENTER)
self.test.wait_for(
@@ -48,5 +48,5 @@ class ListPage:
)
)
def get_list_owner(self):
return self.test.browser.find_element(By.ID, "id_list_owner").text
def get_note_owner(self):
return self.test.browser.find_element(By.ID, "id_note_owner").text

View File

@@ -9,7 +9,7 @@ from apps.applets.models import Applet
class DashboardMaintenanceTest(FunctionalTest):
def setUp(self):
super().setUp()
Applet.objects.get_or_create(slug="new-list", defaults={"name": "New List"})
Applet.objects.get_or_create(slug="new-note", defaults={"name": "New Note"})
Applet.objects.get_or_create(slug="username", defaults={"name": "Username"})
Applet.objects.get_or_create(slug="palette", defaults={"name": "Palette"})

View File

@@ -1,25 +1,25 @@
from .base import FunctionalTest
from .list_page import ListPage
from .note_page import NotePage
class LayoutAndStylingTest(FunctionalTest):
def test_layout_and_styling(self):
self.create_pre_authenticated_session("disco@test.io")
self.browser.get(self.live_server_url)
list_page = ListPage(self)
note_page = NotePage(self)
self.browser.set_window_size(1024, 768)
# print("Viewport width:", self.browser.execute_script("return window.innerWidth"))
inputbox = list_page.get_item_input_box()
inputbox = note_page.get_item_input_box()
self.assertAlmostEqual(
inputbox.location['x'] + inputbox.size['width'] / 2,
512,
delta=10,
)
list_page.add_list_item("testing")
inputbox = list_page.get_item_input_box()
note_page.add_note_item("testing")
inputbox = note_page.get_item_input_box()
self.assertAlmostEqual(
inputbox.location['x'] + inputbox.size['width'] / 2,
512,

View File

@@ -1,36 +1,36 @@
from selenium.webdriver.common.by import By
from .base import FunctionalTest
from .list_page import ListPage
from .my_lists_page import MyListsPage
from .note_page import NotePage
from .my_notes_page import MyNotesPage
class MyListsTest(FunctionalTest):
class MyNotesTest(FunctionalTest):
def test_logged_in_users_lists_are_saved_as_my_lists(self):
def test_logged_in_users_notes_are_saved_as_my_notes(self):
self.create_pre_authenticated_session("disco@test.io")
self.browser.get(self.live_server_url)
list_page = ListPage(self)
list_page.add_list_item("Reticulate splines")
list_page.add_list_item("Regurgitate spines")
first_list_url = self.browser.current_url
note_page = NotePage(self)
note_page.add_note_item("Reticulate splines")
note_page.add_note_item("Regurgitate spines")
first_note_url = self.browser.current_url
MyListsPage(self).go_to_my_lists_page("disco@test.io")
MyNotesPage(self).go_to_my_notes_page("disco@test.io")
self.wait_for(
lambda: self.browser.find_element(By.LINK_TEXT, "Reticulate splines")
)
self.browser.find_element(By.LINK_TEXT, "Reticulate splines").click()
self.wait_for(
lambda: self.assertEqual(self.browser.current_url, first_list_url)
lambda: self.assertEqual(self.browser.current_url, first_note_url)
)
self.browser.get(self.live_server_url)
list_page.add_list_item("Ribbon of death")
second_list_url = self.browser.current_url
note_page.add_note_item("Ribbon of death")
second_note_url = self.browser.current_url
MyListsPage(self).go_to_my_lists_page("disco@test.io")
MyNotesPage(self).go_to_my_notes_page("disco@test.io")
self.wait_for(
lambda: self.browser.find_element(By.LINK_TEXT, "Ribbon of death")
)
@@ -38,7 +38,7 @@ class MyListsTest(FunctionalTest):
self.browser.find_element(By.CSS_SELECTOR, "#id_logout").click()
self.wait_for(
lambda: self.assertEqual(
self.browser.find_elements(By.LINK_TEXT, "My lists"),
self.browser.find_elements(By.LINK_TEXT, "My notes"),
[],
)
)

View File

@@ -2,7 +2,7 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from .base import FunctionalTest
from .list_page import ListPage
from .note_page import NotePage
class ItemValidationTest(FunctionalTest):
@@ -11,69 +11,69 @@ class ItemValidationTest(FunctionalTest):
return self.browser.find_element(By.CSS_SELECTOR, ".invalid-feedback")
# Test methods
def test_cannot_add_empty_list_items(self):
def test_cannot_add_empty_note_items(self):
self.create_pre_authenticated_session("disco@test.io")
self.browser.get(self.live_server_url)
list_page = ListPage(self)
list_page.get_item_input_box().send_keys(Keys.ENTER)
note_page = NotePage(self)
note_page.get_item_input_box().send_keys(Keys.ENTER)
self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_text:invalid")
)
list_page.get_item_input_box().send_keys("Purchase milk")
note_page.get_item_input_box().send_keys("Purchase milk")
self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_text:valid")
)
list_page.get_item_input_box().send_keys(Keys.ENTER)
list_page.wait_for_row_in_list_table("Purchase milk", 1)
note_page.get_item_input_box().send_keys(Keys.ENTER)
note_page.wait_for_row_in_note_table("Purchase milk", 1)
list_page.get_item_input_box().send_keys(Keys.ENTER)
note_page.get_item_input_box().send_keys(Keys.ENTER)
list_page.wait_for_row_in_list_table("Purchase milk", 1)
note_page.wait_for_row_in_note_table("Purchase milk", 1)
self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_text:invalid")
)
list_page.get_item_input_box().send_keys("Make tea")
note_page.get_item_input_box().send_keys("Make tea")
self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR,
"#id_text:valid",
)
)
list_page.get_item_input_box().send_keys(Keys.ENTER)
list_page.wait_for_row_in_list_table("Make tea", 2)
note_page.get_item_input_box().send_keys(Keys.ENTER)
note_page.wait_for_row_in_note_table("Make tea", 2)
def test_cannot_add_duplicate_items(self):
self.create_pre_authenticated_session("disco@test.io")
self.browser.get(self.live_server_url)
list_page = ListPage(self)
list_page.add_list_item("Witness divinity")
note_page = NotePage(self)
note_page.add_note_item("Witness divinity")
list_page.get_item_input_box().send_keys("Witness divinity")
list_page.get_item_input_box().send_keys(Keys.ENTER)
note_page.get_item_input_box().send_keys("Witness divinity")
note_page.get_item_input_box().send_keys(Keys.ENTER)
self.wait_for(
lambda: self.assertEqual(
self.get_error_element().text,
"You've already logged this to your list",
"You've already logged this to your note",
)
)
def test_error_messages_are_cleared_on_input(self):
self.create_pre_authenticated_session("disco@test.io")
self.browser.get(self.live_server_url)
list_page = ListPage(self)
list_page.add_list_item("Gobbledygook")
list_page.get_item_input_box().send_keys("Gobbledygook")
list_page.get_item_input_box().send_keys(Keys.ENTER)
note_page = NotePage(self)
note_page.add_note_item("Gobbledygook")
note_page.get_item_input_box().send_keys("Gobbledygook")
note_page.get_item_input_box().send_keys(Keys.ENTER)
self.wait_for(
lambda: self.assertTrue(self.get_error_element().is_displayed())
)
list_page.get_item_input_box().send_keys("a")
note_page.get_item_input_box().send_keys("a")
self.wait_for(
lambda: self.assertFalse(self.get_error_element().is_displayed())

View File

@@ -5,8 +5,8 @@ from selenium import webdriver
from selenium.webdriver.common.by import By
from .base import FunctionalTest
from .list_page import ListPage
from .my_lists_page import MyListsPage
from .note_page import NotePage
from .my_notes_page import MyNotesPage
# Helper fns
@@ -19,7 +19,7 @@ def quit_if_possible(browser):
# Test mdls
class SharingTest(FunctionalTest):
def test_can_share_a_list_with_another_user(self):
def test_can_share_a_note_with_another_user(self):
self.create_pre_authenticated_session("disco@test.io")
disco_browser = self.browser
self.addCleanup(lambda: quit_if_possible(disco_browser))
@@ -34,40 +34,39 @@ class SharingTest(FunctionalTest):
self.browser = disco_browser
self.browser.get(self.live_server_url)
list_page = ListPage(self).add_list_item("Send help")
note_page = NotePage(self).add_note_item("Send help")
share_box = list_page.get_share_box()
share_box = note_page.get_share_box()
self.assertEqual(
share_box.get_attribute("placeholder"),
"friend@example.com",
)
list_page.share_list_with("alice@test.io")
note_page.share_note_with("alice@test.io")
self.browser = ali_browser
MyListsPage(self).go_to_my_lists_page("alice@test.io")
MyNotesPage(self).go_to_my_notes_page("alice@test.io")
self.browser.find_element(By.LINK_TEXT, "Send help").click()
self.wait_for(
lambda: self.assertEqual(list_page.get_list_owner(), "disco@test.io")
lambda: self.assertEqual(note_page.get_note_owner(), "disco@test.io")
)
list_page.add_list_item("At your command, Disco King")
note_page.add_note_item("At your command, Disco King")
self.browser = disco_browser
self.browser.refresh()
list_page.wait_for_row_in_list_table("At your command, Disco King", 2)
note_page.wait_for_row_in_note_table("At your command, Disco King", 2)
class ListAccessTest(FunctionalTest):
def test_stranger_cannot_access_owned_list(self):
class NoteAccessTest(FunctionalTest):
def test_stranger_cannot_access_owned_note(self):
self.create_pre_authenticated_session("disco@test.io")
self.browser.get(self.live_server_url)
list_page = ListPage(self).add_list_item("private eye")
list_url = self.browser.current_url
note_page = NotePage(self).add_note_item("private eye")
note_url = self.browser.current_url
self.browser.delete_cookie(settings.SESSION_COOKIE_NAME)
self.browser.get(list_url)
self.assertNotEqual(self.browser.current_url, list_url)
self.browser.get(note_url)
self.assertNotEqual(self.browser.current_url, note_url)

View File

@@ -2,7 +2,7 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from .base import FunctionalTest
from .list_page import ListPage
from .note_page import NotePage
class NewVisitorTest(FunctionalTest):
@@ -10,51 +10,51 @@ class NewVisitorTest(FunctionalTest):
def test_can_start_a_todo_list(self):
self.create_pre_authenticated_session("alice@test.io")
self.browser.get(self.live_server_url)
list_page = ListPage(self)
note_page = NotePage(self)
self.assertIn('Earthman RPG', self.browser.title)
header_text = self.browser.find_element(By.TAG_NAME, 'h1').text
self.assertIn('Welcome', header_text)
inputbox = list_page.get_item_input_box()
inputbox = note_page.get_item_input_box()
self.assertEqual(inputbox.get_attribute('placeholder'), 'Enter a to-do item')
inputbox.send_keys('Buy peacock feathers')
inputbox.send_keys(Keys.ENTER)
list_page.wait_for_row_in_list_table("Buy peacock feathers", 1)
note_page.wait_for_row_in_note_table("Buy peacock feathers", 1)
list_page.add_list_item("Use peacock feathers to make a fly")
note_page.add_note_item("Use peacock feathers to make a fly")
list_page.wait_for_row_in_list_table("Use peacock feathers to make a fly", 2)
list_page.wait_for_row_in_list_table("Buy peacock feathers", 1)
note_page.wait_for_row_in_note_table("Use peacock feathers to make a fly", 2)
note_page.wait_for_row_in_note_table("Buy peacock feathers", 1)
def test_multiple_users_can_start_lists_at_different_urls(self):
def test_multiple_users_can_start_notes_at_different_urls(self):
self.create_pre_authenticated_session("alice@test.io")
self.browser.get(self.live_server_url)
list_page = ListPage(self)
list_page.add_list_item("Buy peacock feathers")
note_page = NotePage(self)
note_page.add_note_item("Buy peacock feathers")
edith_dash_url = self.browser.current_url
self.assertRegex(
edith_dash_url,
r'/dashboard/list/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/$',
r'/dashboard/note/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/$',
)
self.browser.delete_all_cookies()
self.create_pre_authenticated_session("disco@test.io")
self.browser.get(self.live_server_url)
list_page = ListPage(self)
note_page = NotePage(self)
page_text = self.browser.find_element(By.TAG_NAME, 'body').text
self.assertNotIn('Buy peacock feathers', page_text)
list_page.add_list_item("Buy milk")
note_page.add_note_item("Buy milk")
francis_dash_url = self.browser.current_url
self.assertRegex(
francis_dash_url,
r'/dashboard/list/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/$',
r'/dashboard/note/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/$',
)
self.assertNotEqual(francis_dash_url, edith_dash_url)

View File

@@ -1,17 +0,0 @@
<section
id="id_applet_my_lists"
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
>
<a href="{% url 'my_lists' user.id %}" class="my-lists-main">My lists:</a>
<div class="my-lists-container">
<ul>
{% for list in recent_lists %}
<li>
<a href="{{ list.get_absolute_url }}">{{ list.name }}</a>
</li>
{% empty %}
<li>No lists yet.</li>
{% endfor %}
</ul>
</div>
</section>

View File

@@ -0,0 +1,17 @@
<section
id="id_applet_my_notes"
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
>
<a href="{% url 'my_notes' user.id %}" class="my-notes-main">My notes:</a>
<div class="my-notes-container">
<ul>
{% for note in recent_notes %}
<li>
<a href="{{ note.get_absolute_url }}">{{ note.name }}</a>
</li>
{% empty %}
<li>No notes yet.</li>
{% endfor %}
</ul>
</div>
</section>

View File

@@ -1,8 +1,8 @@
<section
id="id_applet_new_list"
id="id_applet_new_note"
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
>
<h2>Start a new to-do list</h2>
{% url "new_list" as form_action %}
{% url "new_note" as form_action %}
{% include "apps/dashboard/_partials/_form.html" with form=form form_action=form_action %}
</section>

View File

@@ -1,19 +0,0 @@
{% extends "core/base.html" %}
{% load lyric_extras %}
{% block header_text %}{{ user|display_name }}'s lists{% endblock header_text %}
{% block content %}
<h3>{{ owner|display_name }}'s lists</h3>
<ul>
{% for list in owner.lists.all %}
<li><a href="{{ list.get_absolute_url }}">{{ list.name }}</a></li>
{% endfor %}
</ul>
<h3>Lists shared with me</h3>
<ul>
{% for list in owner.shared_lists.all %}
<li><a href="{{ list.get_absolute_url }}">{{ list.name }}</a></li>
{% endfor %}
</ul>
{% endblock content %}

View File

@@ -0,0 +1,19 @@
{% extends "core/base.html" %}
{% load lyric_extras %}
{% block header_text %}{{ user|display_name }}'s notes{% endblock header_text %}
{% block content %}
<h3>{{ owner|display_name }}'s notes</h3>
<ul>
{% for note in owner.notes.all %}
<li><a href="{{ note.get_absolute_url }}">{{ note.name }}</a></li>
{% endfor %}
</ul>
<h3>Notes shared with me</h3>
<ul>
{% for note in owner.shared_notes.all %}
<li><a href="{{ note.get_absolute_url }}">{{ note.name }}</a></li>
{% endfor %}
</ul>
{% endblock content %}

View File

@@ -6,16 +6,16 @@
{% block extra_header %}
{% url "view_list" list.id as form_action %}
{% url "view_note" note.id as form_action %}
{% include "apps/dashboard/_partials/_form.html" with form=form form_action=form_action %}
{% endblock extra_header %}
{% block content %}
<div class="row justify-content-center">
<div class="col-lg-6">
<small>List created by: <span id="id_list_owner">{{ list.owner|display_name }}</span></small>
<table id="id_list_table" class="table">
{% for item in list.item_set.all %}
<small>List created by: <span id="id_note_owner">{{ note.owner|display_name }}</span></small>
<table id="id_note_table" class="table">
{% for item in note.item_set.all %}
<tr><td>{{ forloop.counter }}. {{ item.text }}</td></tr>
{% endfor %}
</table>
@@ -25,7 +25,7 @@
<div class="row justify-content-center">
<div class="col-lg-6">
<form method="POST" action="{% url "share_list" list.id %}">
<form method="POST" action="{% url "share_note" note.id %}">
{% csrf_token %}
<input
id="id_recipient"
@@ -44,8 +44,8 @@
<button type="submit" class="btn btn-primary btn-xl">Share</button>
</form>
<small>List shared with:
{% for user in list.shared_with.all %}
<span class="list-recipient">{{ user|display_name }}</span>
{% for user in note.shared_with.all %}
<span class="note-recipient">{{ user|display_name }}</span>
{% endfor %}
</small>
</div>