list sharing implemented w. passing UTs & FTs; changes to apps.dashboard.urls, .models, .views & .tests.test_views to accomodate; functional_tests.test_sharing also ensures sharing visible in UX; templates/apps/dashboard/list.html & /my_lists.html updated with django templating & for loops

This commit is contained in:
Disco DeDisco
2026-02-18 13:53:05 -05:00
parent a85e0597d7
commit 0370f36e9e
8 changed files with 92 additions and 2 deletions

View File

@@ -0,0 +1,20 @@
# Generated by Django 6.0 on 2026-02-18 18:13
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0002_list_owner'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='list',
name='shared_with',
field=models.ManyToManyField(blank=True, related_name='shared_lists', to=settings.AUTH_USER_MODEL),
),
]

View File

@@ -10,6 +10,12 @@ class List(models.Model):
on_delete=models.CASCADE, on_delete=models.CASCADE,
) )
shared_with = models.ManyToManyField(
"lyric.User",
related_name="shared_lists",
blank=True,
)
@property @property
def name(self): def name(self):
return self.item_set.first().text return self.item_set.first().text

View File

@@ -1,7 +1,8 @@
import lxml.html import lxml.html
from unittest import skip
from django.test import TestCase from django.test import TestCase
from django.utils import html from django.utils import html
from unittest import skip
from ..forms import ( from ..forms import (
DUPLICATE_ITEM_ERROR, DUPLICATE_ITEM_ERROR,
EMPTY_ITEM_ERROR, EMPTY_ITEM_ERROR,
@@ -9,6 +10,7 @@ from ..forms import (
from ..models import Item, List from ..models import Item, List
from apps.lyric.models import User from apps.lyric.models import User
class HomePageTest(TestCase): class HomePageTest(TestCase):
def test_uses_home_template(self): def test_uses_home_template(self):
response = self.client.get('/') response = self.client.get('/')
@@ -180,3 +182,22 @@ class MyListsTest(TestCase):
response = self.client.get(f"/apps/dashboard/users/{user1.id}/") response = self.client.get(f"/apps/dashboard/users/{user1.id}/")
# assert 403 # assert 403
self.assertEqual(response.status_code, 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()
alice = User.objects.create(email="alice@example.com")
response = self.client.post(
f"/apps/dashboard/{our_list.id}/share_list",
data={"recipient": "alice@example.com"},
)
self.assertRedirects(response, f"/apps/dashboard/{our_list.id}/")
def test_post_with_email_adds_user_to_shared_with(self):
our_list = List.objects.create()
alice = User.objects.create(email="alice@example.com")
self.client.post(
f"/apps/dashboard/{our_list.id}/share_list",
data={"recipient": "alice@example.com"},
)
self.assertIn(alice, our_list.shared_with.all())

View File

@@ -5,4 +5,5 @@ urlpatterns = [
path('new_list', views.new_list, name='new_list'), path('new_list', views.new_list, name='new_list'),
path('<int:list_id>/', views.view_list, name='view_list'), path('<int:list_id>/', views.view_list, name='view_list'),
path('users/<uuid:user_id>/', views.my_lists, name='my_lists'), path('users/<uuid:user_id>/', views.my_lists, name='my_lists'),
path('<int:list_id>/share_list', views.share_list, name="share_list"),
] ]

View File

@@ -37,3 +37,9 @@ def my_lists(request, user_id):
if request.user.id != owner.id: if request.user.id != owner.id:
return HttpResponseForbidden() return HttpResponseForbidden()
return render(request, "apps/dashboard/my_lists.html", {"owner": owner}) return render(request, "apps/dashboard/my_lists.html", {"owner": owner})
def share_list(request, list_id):
our_list = List.objects.get(id=list_id)
recipient = User.objects.get(email=request.POST["recipient"])
our_list.shared_with.add(recipient)
return redirect(our_list)

View File

@@ -36,7 +36,7 @@ class SharingTest(FunctionalTest):
"friend@example.com", "friend@example.com",
) )
list_page.share_list_with("friend@example.com") list_page.share_list_with("alice@example.com")
self.browser = ali_browser self.browser = ali_browser
MyListsPage(self).go_to_my_lists_page("alice@example.com") MyListsPage(self).go_to_my_lists_page("alice@example.com")

View File

@@ -11,6 +11,7 @@
{% block content %} {% block content %}
<div class="row justify-content-center"> <div class="row justify-content-center">
<small>List created by: <span id="id_list_owner">{{ list.owner.email }}</span></small>
<div class="col-lg-6"> <div class="col-lg-6">
<table id="id_list_table" class="table"> <table id="id_list_table" class="table">
{% for item in list.item_set.all %} {% for item in list.item_set.all %}
@@ -19,6 +20,35 @@
</table> </table>
</div> </div>
</div> </div>
<div class="row justify-content-center">
<div class="col-lg-6">
<form method="POST" action="{% url "share_list" list.id %}">
{% csrf_token %}
<input
id="id_recipient"
name="recipient"
class="form-control form-control-lg{% if form.errors %} is-invalid{% endif %}"
placeholder="friend@example.com"
aria-describedby="id_recipient_feedback"
required
/>
{% if form.errors %}
<div id="id_recipient_feedback" class="invalid-feedback">
{{ form.errors.recipient.0 }}
</div>
{% endif %}
<button type="submit" class="btn btn-primary">Share</button>
</form>
<small>List shared with:
{% for user in list.shared_with.all %}
<span class="list-recipient">{{ user.email }}</span>
{% endfor %}
</small>
</div>
</div>
{% endblock content %} {% endblock content %}
{% block scripts %} {% block scripts %}

View File

@@ -9,4 +9,10 @@
<li><a href="{{ list.get_absolute_url }}">{{ list.name }}</a></li> <li><a href="{{ list.get_absolute_url }}">{{ list.name }}</a></li>
{% endfor %} {% endfor %}
</ul> </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 %} {% endblock content %}