diff --git a/container.db.sqlite3 b/container.db.sqlite3 deleted file mode 100644 index f2dafaf..0000000 Binary files a/container.db.sqlite3 and /dev/null differ diff --git a/src/apps/dashboard/migrations/0001_initial.py b/src/apps/dashboard/migrations/0001_initial.py index 87994eb..e9e6dd7 100644 --- a/src/apps/dashboard/migrations/0001_initial.py +++ b/src/apps/dashboard/migrations/0001_initial.py @@ -1,5 +1,6 @@ -# Generated by Django 6.0 on 2025-12-31 05:18 +# Generated by Django 6.0 on 2026-02-08 01:19 +import django.db.models.deletion from django.db import migrations, models @@ -12,9 +13,21 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Item', + name='List', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ], ), + migrations.CreateModel( + name='Item', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('text', models.TextField(default='')), + ('list', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='dashboard.list')), + ], + options={ + 'ordering': ('id',), + 'unique_together': {('list', 'text')}, + }, + ), ] diff --git a/src/apps/dashboard/migrations/0002_item_text.py b/src/apps/dashboard/migrations/0002_item_text.py deleted file mode 100644 index 0c2fab1..0000000 --- a/src/apps/dashboard/migrations/0002_item_text.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 6.0 on 2025-12-31 05:24 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dashboard', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='item', - name='text', - field=models.TextField(default=''), - ), - ] diff --git a/src/apps/dashboard/migrations/0003_list_item_list.py b/src/apps/dashboard/migrations/0003_list_item_list.py deleted file mode 100644 index 33707bb..0000000 --- a/src/apps/dashboard/migrations/0003_list_item_list.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 6.0 on 2026-01-03 03:05 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dashboard', '0002_item_text'), - ] - - operations = [ - migrations.CreateModel( - name='List', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ], - ), - migrations.AddField( - model_name='item', - name='list', - field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='dashboard.list'), - ), - ] diff --git a/src/apps/dashboard/migrations/0004_alter_item_unique_together.py b/src/apps/dashboard/migrations/0004_alter_item_unique_together.py deleted file mode 100644 index fb4619f..0000000 --- a/src/apps/dashboard/migrations/0004_alter_item_unique_together.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 6.0 on 2026-01-24 02:45 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('dashboard', '0003_list_item_list'), - ] - - operations = [ - migrations.AlterUniqueTogether( - name='item', - unique_together={('list', 'text')}, - ), - ] diff --git a/src/apps/dashboard/migrations/0005_alter_item_options.py b/src/apps/dashboard/migrations/0005_alter_item_options.py deleted file mode 100644 index 5fb1062..0000000 --- a/src/apps/dashboard/migrations/0005_alter_item_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 6.0 on 2026-01-24 17:54 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('dashboard', '0004_alter_item_unique_together'), - ] - - operations = [ - migrations.AlterModelOptions( - name='item', - options={'ordering': ('id',)}, - ), - ] diff --git a/src/apps/dashboard/tests/test_views.py b/src/apps/dashboard/tests/test_views.py index e10f20e..6cc1318 100644 --- a/src/apps/dashboard/tests/test_views.py +++ b/src/apps/dashboard/tests/test_views.py @@ -150,4 +150,4 @@ class MyListsTest(TestCase): def test_my_lists_url_renders_my_lists_template(self): user = User.objects.create(email="a@b.cde") response = self.client.get(f"/apps/dashboard/users/{user.id}/") - self.assertTemplateUsed(response, "my-lists.html") + self.assertTemplateUsed(response, "apps/dashboard/my_lists.html") diff --git a/src/apps/dashboard/views.py b/src/apps/dashboard/views.py index 892d784..4d501ed 100644 --- a/src/apps/dashboard/views.py +++ b/src/apps/dashboard/views.py @@ -25,3 +25,5 @@ def view_list(request, list_id): return redirect(our_list) return render(request, "apps/dashboard/list.html", {"list": our_list, "form": form}) +def my_lists(request, user_id): + return render(request, "apps/dashboard/my_lists.html") diff --git a/src/apps/lyric/migrations/0001_initial.py b/src/apps/lyric/migrations/0001_initial.py index 8225462..dbbc8fe 100644 --- a/src/apps/lyric/migrations/0001_initial.py +++ b/src/apps/lyric/migrations/0001_initial.py @@ -1,5 +1,6 @@ -# Generated by Django 6.0 on 2026-01-30 20:02 +# Generated by Django 6.0 on 2026-02-08 01:19 +import uuid from django.db import migrations, models @@ -14,8 +15,15 @@ class Migration(migrations.Migration): migrations.CreateModel( name='User', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('email', models.EmailField(max_length=254, unique=True)), ], ), + migrations.CreateModel( + name='Token', + fields=[ + ('email', models.EmailField(max_length=254)), + ('uid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ], + ), ] diff --git a/src/apps/lyric/migrations/0002_token.py b/src/apps/lyric/migrations/0002_token.py deleted file mode 100644 index bf205e9..0000000 --- a/src/apps/lyric/migrations/0002_token.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 6.0 on 2026-01-30 20:10 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('lyric', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Token', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('email', models.EmailField(max_length=254)), - ('uid', models.UUIDField()), - ], - ), - ] diff --git a/src/apps/lyric/migrations/0003_remove_token_id_alter_token_uid_alter_user_id.py b/src/apps/lyric/migrations/0003_remove_token_id_alter_token_uid_alter_user_id.py deleted file mode 100644 index a94cfca..0000000 --- a/src/apps/lyric/migrations/0003_remove_token_id_alter_token_uid_alter_user_id.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 6.0 on 2026-01-31 01:03 - -import uuid -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('lyric', '0002_token'), - ] - - operations = [ - migrations.RemoveField( - model_name='token', - name='id', - ), - migrations.AlterField( - model_name='token', - name='uid', - field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='user', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - ] diff --git a/src/functional_tests/management/commands/create_session.py b/src/functional_tests/management/commands/create_session.py index b96bf65..215b93c 100644 --- a/src/functional_tests/management/commands/create_session.py +++ b/src/functional_tests/management/commands/create_session.py @@ -17,7 +17,7 @@ class Command(BaseCommand): def create_pre_authenticated_session(email): user = User.objects.create(email=email) session = SessionStore() - session[SESSION_KEY] = user.pk + session[SESSION_KEY] = str(user.pk) # Convert UUID to string for JSON serialization session[BACKEND_SESSION_KEY] = settings.AUTHENTICATION_BACKENDS[0] session.save() return session.session_key diff --git a/src/functional_tests/test_login.py b/src/functional_tests/test_login.py index 477b0ac..a12b073 100644 --- a/src/functional_tests/test_login.py +++ b/src/functional_tests/test_login.py @@ -1,5 +1,5 @@ import re -from django.core import mail +from unittest.mock import patch from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from .base import FunctionalTest @@ -8,7 +8,11 @@ TEST_EMAIL = "discoman@example.com" SUBJECT = "A magic login link to your Dashboard" class LoginTest(FunctionalTest): - def test_login_using_magic_link(self): + @patch('apps.lyric.views.requests.post') + def test_login_using_magic_link(self, mock_post): + # Mock successful Mailgun API response + mock_post.return_value.status_code = 200 + self.browser.get(self.live_server_url) self.browser.find_element(By.CSS_SELECTOR, "input[name=email]").send_keys( TEST_EMAIL, Keys.ENTER @@ -24,14 +28,20 @@ class LoginTest(FunctionalTest): if self.test_server: return - email = mail.outbox.pop() - self.assertIn(TEST_EMAIL, email.to) - self.assertEqual(email.subject, SUBJECT) + # Verify Mailgun API was called + self.assertEqual(mock_post.call_count, 1) + call_kwargs = mock_post.call_args.kwargs - self.assertIn("Use this magic link to login to your Dashboard", email.body) - url_search = re.search(r"http://.+/.+$", email.body) + # Check email data + self.assertEqual(call_kwargs['data']['to'], TEST_EMAIL) + self.assertEqual(call_kwargs['data']['subject'], SUBJECT) + + # Extract magic link URL from email body + email_body = call_kwargs['data']['text'] + self.assertIn("Use this magic link to login to your Dashboard", email_body) + url_search = re.search(r"http://.+/.+$", email_body) if not url_search: - self.fail(f"Could not find url in email body:\n{email.body}") + self.fail(f"Could not find url in email body:\n{email_body}") url = url_search.group(0) self.assertIn(self.live_server_url, url) diff --git a/src/functional_tests/test_my_lists.py b/src/functional_tests/test_my_lists.py index 716f726..a122a34 100644 --- a/src/functional_tests/test_my_lists.py +++ b/src/functional_tests/test_my_lists.py @@ -35,10 +35,12 @@ class MyListsTest(FunctionalTest): self.wait_for( lambda: self.assertIn( "discoman@example.com", - self.browser.find_element(By.CSS_SELECTOR, "h1").text, + self.browser.find_element(By.CSS_SELECTOR, "h2").text, ) ) + return # TODO: resume here after templates refactor + self.wait_for( lambda: self.browser.find_element(By.LINK_TEXT, "Reticulate splines") ) diff --git a/src/templates/apps/dashboard/my_lists.html b/src/templates/apps/dashboard/my_lists.html new file mode 100644 index 0000000..6625362 --- /dev/null +++ b/src/templates/apps/dashboard/my_lists.html @@ -0,0 +1,3 @@ +{% extends "core/base.html" %} + +{% block header_text %}{{ user.email }}'s lists{% endblock header_text %} diff --git a/src/templates/core/base.html b/src/templates/core/base.html index a2387fa..d7128cd 100644 --- a/src/templates/core/base.html +++ b/src/templates/core/base.html @@ -18,7 +18,7 @@

Welcome, Earthman

{% if user.email %} - My lists + My lists Logged in as {{ user.email }}
{% csrf_token %}