mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-23 01:51:08 +00:00
Merge pull request #1869 from bookwyrm-social/list-notes
Let users add info about their list entry submissions
This commit is contained in:
commit
b3d9a46c98
25 changed files with 1225 additions and 790 deletions
|
@ -444,6 +444,12 @@ class ListForm(CustomForm):
|
|||
fields = ["user", "name", "description", "curation", "privacy", "group"]
|
||||
|
||||
|
||||
class ListItemForm(CustomForm):
|
||||
class Meta:
|
||||
model = models.ListItem
|
||||
fields = ["user", "book", "book_list", "notes"]
|
||||
|
||||
|
||||
class GroupForm(CustomForm):
|
||||
class Meta:
|
||||
model = models.Group
|
||||
|
|
21
bookwyrm/migrations/0130_alter_listitem_notes.py
Normal file
21
bookwyrm/migrations/0130_alter_listitem_notes.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 3.2.10 on 2022-01-24 20:01
|
||||
|
||||
import bookwyrm.models.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0129_auto_20220117_1716"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="listitem",
|
||||
name="notes",
|
||||
field=bookwyrm.models.fields.TextField(
|
||||
blank=True, max_length=300, null=True
|
||||
),
|
||||
),
|
||||
]
|
13
bookwyrm/migrations/0131_merge_20220125_1644.py
Normal file
13
bookwyrm/migrations/0131_merge_20220125_1644.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Generated by Django 3.2.10 on 2022-01-25 16:44
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0130_alter_listitem_notes"),
|
||||
("bookwyrm", "0130_alter_user_preferred_language"),
|
||||
]
|
||||
|
||||
operations = []
|
|
@ -2,6 +2,7 @@
|
|||
import uuid
|
||||
|
||||
from django.apps import apps
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
|
@ -74,6 +75,22 @@ class List(OrderedCollectionMixin, BookWyrmModel):
|
|||
return
|
||||
super().raise_not_editable(viewer)
|
||||
|
||||
def raise_not_submittable(self, viewer):
|
||||
"""can the user submit a book to the list?"""
|
||||
# if you can't view the list you can't submit to it
|
||||
self.raise_visible_to_user(viewer)
|
||||
|
||||
# all good if you're the owner or the list is open
|
||||
if self.user == viewer or self.curation in ["open", "curated"]:
|
||||
return
|
||||
if self.curation == "group":
|
||||
is_group_member = GroupMember.objects.filter(
|
||||
group=self.group, user=viewer
|
||||
).exists()
|
||||
if is_group_member:
|
||||
return
|
||||
raise PermissionDenied()
|
||||
|
||||
@classmethod
|
||||
def followers_filter(cls, queryset, viewer):
|
||||
"""Override filter for "followers" privacy level to allow non-following
|
||||
|
@ -125,7 +142,7 @@ class ListItem(CollectionItemMixin, BookWyrmModel):
|
|||
user = fields.ForeignKey(
|
||||
"User", on_delete=models.PROTECT, activitypub_field="actor"
|
||||
)
|
||||
notes = fields.TextField(blank=True, null=True)
|
||||
notes = fields.TextField(blank=True, null=True, max_length=300)
|
||||
approved = models.BooleanField(default=True)
|
||||
order = fields.IntegerField()
|
||||
endorsement = models.ManyToManyField("User", related_name="endorsers")
|
||||
|
|
45
bookwyrm/templates/lists/add_item_modal.html
Normal file
45
bookwyrm/templates/lists/add_item_modal.html
Normal file
|
@ -0,0 +1,45 @@
|
|||
{% extends 'components/modal.html' %}
|
||||
{% load i18n %}
|
||||
{% load utilities %}
|
||||
{% load group_tags %}
|
||||
|
||||
{% block modal-title %}
|
||||
{% if list.curation == 'open' or request.user == list.user or list.group|is_member:request.user %}
|
||||
{% blocktrans trimmed with title=book|book_title %}
|
||||
Add "<em>{{ title }}</em>" to this list
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed with title=book|book_title %}
|
||||
Suggest "<em>{{ title }}</em>" for this list
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-form-open %}
|
||||
<form
|
||||
name="add-book-{{ book.id }}"
|
||||
method="POST"
|
||||
action="{% url 'list-add-book' %}{% if query %}?q={{ query }}{% endif %}"
|
||||
>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="book" value="{{ book.id }}">
|
||||
<input type="hidden" name="book_list" value="{{ list.id }}">
|
||||
<input type="hidden" name="user" value="{{ request.user.id }}">
|
||||
{% include "lists/item_notes_field.html" with form_id=id show_label=True %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<button type="submit" class="button is-link">
|
||||
{% if list.curation == 'open' or request.user == list.user or list.group|is_member:request.user %}
|
||||
{% trans "Add" %}
|
||||
{% else %}
|
||||
{% trans "Suggest" %}
|
||||
{% endif %}
|
||||
</button>
|
||||
<button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-form-close %}</form>{% endblock %}
|
|
@ -1,5 +1,6 @@
|
|||
{% extends 'lists/layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load utilities %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
|
||||
|
@ -38,6 +39,17 @@
|
|||
|
||||
<div class="column ml-3">
|
||||
{% include 'snippets/book_titleby.html' %}
|
||||
{% if item.notes %}
|
||||
<div>
|
||||
{% url 'user-feed' item.user|username as user_path %}
|
||||
{% blocktrans trimmed with username=item.user.display_name %}
|
||||
<a href="{{ user_path }}">{{ username }}</a> says:
|
||||
{% endblocktrans %}
|
||||
<p class="notification">
|
||||
{{ item.notes }}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
{% trans "Suggested by" %}
|
||||
|
|
20
bookwyrm/templates/lists/edit_item_form.html
Normal file
20
bookwyrm/templates/lists/edit_item_form.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% load i18n %}
|
||||
<form
|
||||
name="edit-notes-{{ item.id }}"
|
||||
method="POST"
|
||||
action="{% url 'list-item' list.id item.id %}"
|
||||
>
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="book" value="{{ book.id }}">
|
||||
<input type="hidden" name="book_list" value="{{ list.id }}">
|
||||
<input type="hidden" name="user" value="{{ request.user.id }}">
|
||||
{% include "lists/item_notes_field.html" with form_id=item.id %}
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-success">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
21
bookwyrm/templates/lists/item_notes_field.html
Normal file
21
bookwyrm/templates/lists/item_notes_field.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
{% load i18n %}
|
||||
<div class="field">
|
||||
<label
|
||||
for="id_notes_{{ form_id }}"
|
||||
class="{% if show_label %}label{% else %}is-sr-only{% endif %}"
|
||||
>
|
||||
{% trans "Notes:" %}
|
||||
</label>
|
||||
<div class="control">
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="id_notes_{{ form_id }}"
|
||||
maxlength="300"
|
||||
name="notes"
|
||||
aria-describedby="notes_description_{{ form_id }}"
|
||||
>{{ item.notes|default:'' }}</textarea>
|
||||
</div>
|
||||
<p class="help" id="notes_description_{{ form_id }}">
|
||||
{% trans "An optional note that will be displayed with the book." %}
|
||||
</p>
|
||||
</div>
|
|
@ -1,9 +1,10 @@
|
|||
{% extends 'lists/layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load rating_tags %}
|
||||
{% load book_display_tags %}
|
||||
{% load group_tags %}
|
||||
{% load book_display_tags %}
|
||||
{% load markdown %}
|
||||
{% load utilities %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
|
||||
|
@ -21,7 +22,7 @@
|
|||
{% block panel %}
|
||||
{% if request.user == list.user and pending_count %}
|
||||
<div class="block content">
|
||||
<p>
|
||||
<p class="notification">
|
||||
<a href="{% url 'list-curate' list.id %}">{{ pending_count }} book{{ pending_count|pluralize }} awaiting your approval</a>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -46,21 +47,16 @@
|
|||
{% for item in items %}
|
||||
<li class="block mb-5">
|
||||
<div class="card">
|
||||
{% with book=item.book %}
|
||||
<div
|
||||
class="
|
||||
card-content p-0 mb-0
|
||||
columns is-gapless
|
||||
is-mobile
|
||||
"
|
||||
>
|
||||
<div class="column is-3-mobile is-2-tablet is-cover align to-t">
|
||||
<div class="card-content">
|
||||
{% with book=item.book %}
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-narrow is-cover">
|
||||
<a href="{{ item.book.local_path }}" aria-hidden="true">
|
||||
{% include 'snippets/book_cover.html' with cover_class='is-w-auto is-h-m-tablet is-align-items-flex-start' size='medium' %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="column mx-3 my-2">
|
||||
<div class="column">
|
||||
<p>
|
||||
{% include 'snippets/book_titleby.html' %}
|
||||
</p>
|
||||
|
@ -73,8 +69,54 @@
|
|||
{% include 'snippets/shelve_button/shelve_button.html' with book=book %}
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
|
||||
{% if item.notes %}
|
||||
<div class="media notification">
|
||||
<figure class="media-left" aria-hidden="true">
|
||||
{% include "snippets/avatar.html" with user=item.user %}
|
||||
</figure>
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<header>
|
||||
{% url 'user-feed' user|username as user_path %}
|
||||
{% blocktrans trimmed with username=user.display_name %}
|
||||
<a href="{{ user_path }}">{{ username }}</a> says:
|
||||
{% endblocktrans %}
|
||||
</header>
|
||||
<p>
|
||||
{{ item.notes|to_markdown|safe }}
|
||||
</p>
|
||||
</div>
|
||||
{% if item.user == request.user %}
|
||||
<div>
|
||||
<details class="details-panel box">
|
||||
<summary>
|
||||
<span role="heading" aria-level="3">
|
||||
{% trans "Edit notes" %}
|
||||
<span class="details-close icon icon-pencil" aria-hidden></span>
|
||||
</span>
|
||||
</summary>
|
||||
{% include "lists/edit_item_form.html" %}
|
||||
</details>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% elif item.user == request.user %}
|
||||
<div>
|
||||
<details class="details-panel box">
|
||||
<summary>
|
||||
<span role="heading" aria-level="3">
|
||||
{% trans "Add notes" %}
|
||||
<span class="details-close icon icon-plus" aria-hidden></span>
|
||||
</span>
|
||||
</summary>
|
||||
{% include "lists/edit_item_form.html" %}
|
||||
</details>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-footer is-stacked-mobile has-background-white-bis is-align-items-stretch">
|
||||
<div class="card-footer-item">
|
||||
<p>
|
||||
|
@ -195,23 +237,19 @@
|
|||
<div class="column ml-3">
|
||||
<p>{% include 'snippets/book_titleby.html' with book=book %}</p>
|
||||
|
||||
<form
|
||||
class="mt-1"
|
||||
name="add-book-{{ book.id }}"
|
||||
method="post"
|
||||
action="{% url 'list-add-book' %}{% if query %}?q={{ query }}{% endif %}"
|
||||
{% join "add_item" list.id book.id as modal_id %}
|
||||
<button
|
||||
type="button"
|
||||
class="button is-small is-link"
|
||||
data-modal-open="{{ modal_id }}"
|
||||
>
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="book" value="{{ book.id }}">
|
||||
<input type="hidden" name="list" value="{{ list.id }}">
|
||||
<button type="submit" class="button is-small is-link">
|
||||
{% if list.curation == 'open' or request.user == list.user or list.group|is_member:request.user %}
|
||||
{% trans "Add" %}
|
||||
{% else %}
|
||||
{% trans "Suggest" %}
|
||||
{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
{% if list.curation == 'open' or request.user == list.user or list.group|is_member:request.user %}
|
||||
{% trans "Add" %}
|
||||
{% else %}
|
||||
{% trans "Suggest" %}
|
||||
{% endif %}
|
||||
</button>
|
||||
{% include "lists/add_item_modal.html" with id=modal_id %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
@ -225,7 +263,7 @@
|
|||
<textarea
|
||||
readonly
|
||||
class="textarea is-small"
|
||||
aria-labelledby="embed-label"
|
||||
aria-describedby="embed-label"
|
||||
data-copytext
|
||||
data-copytext-label="{% trans 'Copy embed code' %}"
|
||||
data-copytext-success="{% trans 'Copied!' %}"
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
{% load static %}
|
||||
|
||||
<img class="avatar image {% if large %}is-96x96{% elif medium %}is-48x48{% else %}is-32x32{% endif %}" src="{% if user.avatar %}{% get_media_prefix %}{{ user.avatar }}{% else %}{% static "images/default_avi.jpg" %}{% endif %}" {% if ariaHide %}aria-hidden="true"{% endif %} alt="{{ user.alt_text }}">
|
||||
|
||||
<img
|
||||
class="avatar image {% if large %}is-96x96{% elif medium %}is-48x48{% else %}is-32x32{% endif %}"
|
||||
src="{% if user.avatar %}{% get_media_prefix %}{{ user.avatar }}{% else %}{% static "images/default_avi.jpg" %}{% endif %}"
|
||||
{% if ariaHide %}aria-hidden="true"{% endif %}
|
||||
alt="{{ user.alt_text }}"
|
||||
>
|
||||
|
|
|
@ -118,6 +118,7 @@ class InboxAdd(TestCase):
|
|||
"type": "ListItem",
|
||||
"book": self.book.remote_id,
|
||||
"id": "https://example.com/listbook/6189",
|
||||
"notes": "hi hello",
|
||||
"order": 1,
|
||||
},
|
||||
"target": "https://example.com/user/mouse/list/to-read",
|
||||
|
@ -130,3 +131,4 @@ class InboxAdd(TestCase):
|
|||
self.assertEqual(booklist.name, "Test List")
|
||||
self.assertEqual(booklist.books.first(), self.book)
|
||||
self.assertEqual(listitem.remote_id, "https://example.com/listbook/6189")
|
||||
self.assertEqual(listitem.notes, "hi hello")
|
||||
|
|
1
bookwyrm/tests/views/lists/__init__.py
Normal file
1
bookwyrm/tests/views/lists/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from . import *
|
129
bookwyrm/tests/views/lists/test_curate.py
Normal file
129
bookwyrm/tests/views/lists/test_curate.py
Normal file
|
@ -0,0 +1,129 @@
|
|||
""" test for app action functionality """
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from bookwyrm import models, views
|
||||
from bookwyrm.tests.validate_html import validate_html
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
class ListViews(TestCase):
|
||||
"""list view"""
|
||||
|
||||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||
), patch("bookwyrm.lists_stream.populate_lists_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.com",
|
||||
"mouseword",
|
||||
local=True,
|
||||
localname="mouse",
|
||||
remote_id="https://example.com/users/mouse",
|
||||
)
|
||||
work = models.Work.objects.create(title="Work")
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Example Edition",
|
||||
remote_id="https://example.com/book/1",
|
||||
parent_work=work,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
self.list = models.List.objects.create(
|
||||
name="Test List", user=self.local_user
|
||||
)
|
||||
self.anonymous_user = AnonymousUser
|
||||
self.anonymous_user.is_authenticated = False
|
||||
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_curate_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.Curate.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=False,
|
||||
order=1,
|
||||
)
|
||||
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
request.user = self.anonymous_user
|
||||
result = view(request, self.list.id)
|
||||
self.assertEqual(result.status_code, 302)
|
||||
|
||||
def test_curate_approve(self):
|
||||
"""approve a pending item"""
|
||||
view = views.Curate.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
pending = models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=False,
|
||||
order=1,
|
||||
)
|
||||
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{"item": pending.id, "approved": "true"},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
view(request, self.list.id)
|
||||
|
||||
self.assertEqual(mock.call_count, 2)
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["target"], self.list.remote_id)
|
||||
|
||||
pending.refresh_from_db()
|
||||
self.assertEqual(self.list.books.count(), 1)
|
||||
self.assertEqual(self.list.listitem_set.first(), pending)
|
||||
self.assertTrue(pending.approved)
|
||||
|
||||
def test_curate_reject(self):
|
||||
"""approve a pending item"""
|
||||
view = views.Curate.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
pending = models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=False,
|
||||
order=1,
|
||||
)
|
||||
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"item": pending.id,
|
||||
"approved": "false",
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
view(request, self.list.id)
|
||||
|
||||
self.assertFalse(self.list.books.exists())
|
||||
self.assertFalse(models.ListItem.objects.exists())
|
89
bookwyrm/tests/views/lists/test_embed.py
Normal file
89
bookwyrm/tests/views/lists/test_embed.py
Normal file
|
@ -0,0 +1,89 @@
|
|||
""" test for app action functionality """
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.http.response import Http404
|
||||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from bookwyrm import models, views
|
||||
from bookwyrm.tests.validate_html import validate_html
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
class ListViews(TestCase):
|
||||
"""list view"""
|
||||
|
||||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||
), patch("bookwyrm.lists_stream.populate_lists_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.com",
|
||||
"mouseword",
|
||||
local=True,
|
||||
localname="mouse",
|
||||
remote_id="https://example.com/users/mouse",
|
||||
)
|
||||
work = models.Work.objects.create(title="Work")
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Example Edition",
|
||||
remote_id="https://example.com/book/1",
|
||||
parent_work=work,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
self.list = models.List.objects.create(
|
||||
name="Test List", user=self.local_user
|
||||
)
|
||||
self.anonymous_user = AnonymousUser
|
||||
self.anonymous_user.is_authenticated = False
|
||||
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_embed_call_without_key(self):
|
||||
"""there are so many views, this just makes sure it DOESN’T load"""
|
||||
view = views.unsafe_embed_list
|
||||
request = self.factory.get("")
|
||||
request.user = self.anonymous_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.views.list.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
with self.assertRaises(Http404):
|
||||
view(request, self.list.id, "")
|
||||
|
||||
def test_embed_call_with_key(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.unsafe_embed_list
|
||||
request = self.factory.get("")
|
||||
request.user = self.anonymous_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
|
||||
embed_key = str(self.list.embed_key.hex)
|
||||
|
||||
with patch("bookwyrm.views.list.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id, embed_key)
|
||||
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
|
@ -4,14 +4,19 @@ from unittest.mock import patch
|
|||
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from bookwyrm import models, views
|
||||
from bookwyrm.activitypub import ActivitypubResponse
|
||||
from bookwyrm.tests.validate_html import validate_html
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
class ListActionViews(TestCase):
|
||||
"""tag views"""
|
||||
# pylint: disable=too-many-public-methods
|
||||
class ListViews(TestCase):
|
||||
"""list view"""
|
||||
|
||||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
|
@ -35,7 +40,6 @@ class ListActionViews(TestCase):
|
|||
localname="rat",
|
||||
remote_id="https://example.com/users/rat",
|
||||
)
|
||||
|
||||
work = models.Work.objects.create(title="Work")
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Example Edition",
|
||||
|
@ -67,8 +71,194 @@ class ListActionViews(TestCase):
|
|||
)
|
||||
self.anonymous_user = AnonymousUser
|
||||
self.anonymous_user.is_authenticated = False
|
||||
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_list_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.views.list.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_page_with_query(self):
|
||||
"""searching for a book to add"""
|
||||
view = views.List.as_view()
|
||||
request = self.factory.get("", {"q": "Example Edition"})
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.views.list.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_page_sorted(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
for (i, book) in enumerate([self.book, self.book_two, self.book_three]):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=book,
|
||||
approved=True,
|
||||
order=i + 1,
|
||||
)
|
||||
|
||||
request = self.factory.get("/?sort_by=order")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.list.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
request = self.factory.get("/?sort_by=title")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.list.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
request = self.factory.get("/?sort_by=rating")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.list.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
request = self.factory.get("/?sort_by=sdkfh")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.list.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_page_empty(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.views.list.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_page_logged_out(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
|
||||
request = self.factory.get("")
|
||||
request.user = self.anonymous_user
|
||||
with patch("bookwyrm.views.list.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_page_json_view(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.views.list.list.is_api_request") as is_api:
|
||||
is_api.return_value = True
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_page_json_view_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
request = self.factory.get("/?page=1")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.list.list.is_api_request") as is_api:
|
||||
is_api.return_value = True
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_edit(self):
|
||||
"""edit a list"""
|
||||
view = views.List.as_view()
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"name": "New Name",
|
||||
"description": "wow",
|
||||
"privacy": "direct",
|
||||
"curation": "curated",
|
||||
"user": self.local_user.id,
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
result = view(request, self.list.id)
|
||||
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Update")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["object"]["id"], self.list.remote_id)
|
||||
|
||||
self.assertEqual(result.status_code, 302)
|
||||
|
||||
self.list.refresh_from_db()
|
||||
self.assertEqual(self.list.name, "New Name")
|
||||
self.assertEqual(self.list.description, "wow")
|
||||
self.assertEqual(self.list.privacy, "direct")
|
||||
self.assertEqual(self.list.curation, "curated")
|
||||
|
||||
def test_delete_list(self):
|
||||
"""delete an entire list"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
|
@ -110,73 +300,14 @@ class ListActionViews(TestCase):
|
|||
with self.assertRaises(PermissionDenied):
|
||||
views.delete_list(request, self.list.id)
|
||||
|
||||
def test_curate_approve(self):
|
||||
"""approve a pending item"""
|
||||
view = views.Curate.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
pending = models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=False,
|
||||
order=1,
|
||||
)
|
||||
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{"item": pending.id, "approved": "true"},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
view(request, self.list.id)
|
||||
|
||||
self.assertEqual(mock.call_count, 2)
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["target"], self.list.remote_id)
|
||||
|
||||
pending.refresh_from_db()
|
||||
self.assertEqual(self.list.books.count(), 1)
|
||||
self.assertEqual(self.list.listitem_set.first(), pending)
|
||||
self.assertTrue(pending.approved)
|
||||
|
||||
def test_curate_reject(self):
|
||||
"""approve a pending item"""
|
||||
view = views.Curate.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
pending = models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=False,
|
||||
order=1,
|
||||
)
|
||||
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"item": pending.id,
|
||||
"approved": "false",
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
view(request, self.list.id)
|
||||
|
||||
self.assertFalse(self.list.books.exists())
|
||||
self.assertFalse(models.ListItem.objects.exists())
|
||||
|
||||
def test_add_book(self):
|
||||
"""put a book on a list"""
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
"book_list": self.list.id,
|
||||
"user": self.local_user.id,
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
@ -184,7 +315,7 @@ class ListActionViews(TestCase):
|
|||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
views.list.add_book(request)
|
||||
views.add_book(request)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
|
@ -205,7 +336,8 @@ class ListActionViews(TestCase):
|
|||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
"book_list": self.list.id,
|
||||
"user": self.local_user.id,
|
||||
},
|
||||
)
|
||||
request_one.user = self.local_user
|
||||
|
@ -214,13 +346,14 @@ class ListActionViews(TestCase):
|
|||
"",
|
||||
{
|
||||
"book": self.book_two.id,
|
||||
"list": self.list.id,
|
||||
"book_list": self.list.id,
|
||||
"user": self.local_user.id,
|
||||
},
|
||||
)
|
||||
request_two.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.list.add_book(request_one)
|
||||
views.list.add_book(request_two)
|
||||
views.add_book(request_one)
|
||||
views.add_book(request_two)
|
||||
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
|
@ -237,7 +370,8 @@ class ListActionViews(TestCase):
|
|||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
"book_list": self.list.id,
|
||||
"user": self.local_user.id,
|
||||
},
|
||||
)
|
||||
request_one.user = self.local_user
|
||||
|
@ -246,7 +380,8 @@ class ListActionViews(TestCase):
|
|||
"",
|
||||
{
|
||||
"book": self.book_two.id,
|
||||
"list": self.list.id,
|
||||
"book_list": self.list.id,
|
||||
"user": self.local_user.id,
|
||||
},
|
||||
)
|
||||
request_two.user = self.local_user
|
||||
|
@ -255,15 +390,16 @@ class ListActionViews(TestCase):
|
|||
"",
|
||||
{
|
||||
"book": self.book_three.id,
|
||||
"list": self.list.id,
|
||||
"book_list": self.list.id,
|
||||
"user": self.local_user.id,
|
||||
},
|
||||
)
|
||||
request_three.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.list.add_book(request_one)
|
||||
views.list.add_book(request_two)
|
||||
views.list.add_book(request_three)
|
||||
views.add_book(request_one)
|
||||
views.add_book(request_two)
|
||||
views.add_book(request_three)
|
||||
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
|
@ -276,7 +412,7 @@ class ListActionViews(TestCase):
|
|||
remove_request = self.factory.post("", {"item": items[1].id})
|
||||
remove_request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.list.remove_book(remove_request, self.list.id)
|
||||
views.remove_book(remove_request, self.list.id)
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
self.assertEqual(items[1].book, self.book_three)
|
||||
|
@ -293,7 +429,8 @@ class ListActionViews(TestCase):
|
|||
"",
|
||||
{
|
||||
"book": self.book_three.id,
|
||||
"list": self.list.id,
|
||||
"book_list": self.list.id,
|
||||
"user": self.local_user.id,
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
@ -312,7 +449,7 @@ class ListActionViews(TestCase):
|
|||
approved=False,
|
||||
order=2,
|
||||
)
|
||||
views.list.add_book(request)
|
||||
views.add_book(request)
|
||||
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
|
@ -403,7 +540,8 @@ class ListActionViews(TestCase):
|
|||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
"book_list": self.list.id,
|
||||
"user": self.local_user.id,
|
||||
},
|
||||
)
|
||||
request_one.user = self.local_user
|
||||
|
@ -412,7 +550,8 @@ class ListActionViews(TestCase):
|
|||
"",
|
||||
{
|
||||
"book": self.book_two.id,
|
||||
"list": self.list.id,
|
||||
"book_list": self.list.id,
|
||||
"user": self.local_user.id,
|
||||
},
|
||||
)
|
||||
request_two.user = self.local_user
|
||||
|
@ -421,15 +560,16 @@ class ListActionViews(TestCase):
|
|||
"",
|
||||
{
|
||||
"book": self.book_three.id,
|
||||
"list": self.list.id,
|
||||
"book_list": self.list.id,
|
||||
"user": self.local_user.id,
|
||||
},
|
||||
)
|
||||
request_three.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.list.add_book(request_one)
|
||||
views.list.add_book(request_two)
|
||||
views.list.add_book(request_three)
|
||||
views.add_book(request_one)
|
||||
views.add_book(request_two)
|
||||
views.add_book(request_three)
|
||||
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
|
@ -442,7 +582,7 @@ class ListActionViews(TestCase):
|
|||
set_position_request = self.factory.post("", {"position": 1})
|
||||
set_position_request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.list.set_book_position(set_position_request, items[2].id)
|
||||
views.set_book_position(set_position_request, items[2].id)
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book_three)
|
||||
self.assertEqual(items[1].book, self.book)
|
||||
|
@ -459,7 +599,8 @@ class ListActionViews(TestCase):
|
|||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
"book_list": self.list.id,
|
||||
"user": self.rat.id,
|
||||
},
|
||||
)
|
||||
request.user = self.rat
|
||||
|
@ -467,7 +608,7 @@ class ListActionViews(TestCase):
|
|||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
views.list.add_book(request)
|
||||
views.add_book(request)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
|
@ -487,7 +628,8 @@ class ListActionViews(TestCase):
|
|||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
"book_list": self.list.id,
|
||||
"user": self.rat.id,
|
||||
},
|
||||
)
|
||||
request.user = self.rat
|
||||
|
@ -495,7 +637,7 @@ class ListActionViews(TestCase):
|
|||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
views.list.add_book(request)
|
||||
views.add_book(request)
|
||||
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
|
@ -519,7 +661,8 @@ class ListActionViews(TestCase):
|
|||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
"book_list": self.list.id,
|
||||
"user": self.local_user.id,
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
@ -527,7 +670,7 @@ class ListActionViews(TestCase):
|
|||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
views.list.add_book(request)
|
||||
views.add_book(request)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
|
@ -539,6 +682,23 @@ class ListActionViews(TestCase):
|
|||
self.assertEqual(item.user, self.local_user)
|
||||
self.assertTrue(item.approved)
|
||||
|
||||
def test_add_book_permission_denied(self):
|
||||
"""you can't add to that list"""
|
||||
self.list.curation = "closed"
|
||||
self.list.save(broadcast=False)
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"book_list": self.list.id,
|
||||
"user": self.rat.id,
|
||||
},
|
||||
)
|
||||
request.user = self.rat
|
||||
|
||||
with self.assertRaises(PermissionDenied):
|
||||
views.add_book(request)
|
||||
|
||||
def test_remove_book(self):
|
||||
"""take an item off a list"""
|
||||
|
||||
|
@ -555,7 +715,7 @@ class ListActionViews(TestCase):
|
|||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.list.remove_book(request, self.list.id)
|
||||
views.remove_book(request, self.list.id)
|
||||
self.assertFalse(self.list.listitem_set.exists())
|
||||
|
||||
def test_remove_book_unauthorized(self):
|
||||
|
@ -569,7 +729,7 @@ class ListActionViews(TestCase):
|
|||
request.user = self.rat
|
||||
|
||||
with self.assertRaises(PermissionDenied):
|
||||
views.list.remove_book(request, self.list.id)
|
||||
views.remove_book(request, self.list.id)
|
||||
self.assertTrue(self.list.listitem_set.exists())
|
||||
|
||||
def test_save_unsave_list(self):
|
70
bookwyrm/tests/views/lists/test_list_item.py
Normal file
70
bookwyrm/tests/views/lists/test_list_item.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
""" test for app action functionality """
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from bookwyrm import models, views
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
# pylint: disable=too-many-public-methods
|
||||
class ListItemViews(TestCase):
|
||||
"""list view"""
|
||||
|
||||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||
), patch("bookwyrm.lists_stream.populate_lists_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.com",
|
||||
"mouseword",
|
||||
local=True,
|
||||
localname="mouse",
|
||||
remote_id="https://example.com/users/mouse",
|
||||
)
|
||||
work = models.Work.objects.create(title="Work")
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Example Edition",
|
||||
remote_id="https://example.com/book/1",
|
||||
parent_work=work,
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
self.list = models.List.objects.create(
|
||||
name="Test List", user=self.local_user
|
||||
)
|
||||
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_add_list_item_notes(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.ListItem.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
item = models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book_list": self.list.id,
|
||||
"book": self.book.id,
|
||||
"user": self.local_user.id,
|
||||
"notes": "beep boop",
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
view(request, self.list.id, item.id)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
|
||||
item.refresh_from_db()
|
||||
self.assertEqual(item.notes, "beep boop")
|
161
bookwyrm/tests/views/lists/test_lists.py
Normal file
161
bookwyrm/tests/views/lists/test_lists.py
Normal file
|
@ -0,0 +1,161 @@
|
|||
""" test for app action functionality """
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from bookwyrm import models, views
|
||||
from bookwyrm.tests.validate_html import validate_html
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
class ListViews(TestCase):
|
||||
"""lists of lists"""
|
||||
|
||||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||
), patch("bookwyrm.lists_stream.populate_lists_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.com",
|
||||
"mouseword",
|
||||
local=True,
|
||||
localname="mouse",
|
||||
remote_id="https://example.com/users/mouse",
|
||||
)
|
||||
self.anonymous_user = AnonymousUser
|
||||
self.anonymous_user.is_authenticated = False
|
||||
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
@patch("bookwyrm.lists_stream.ListsStream.get_list_stream")
|
||||
def test_lists_page(self, _):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.Lists.as_view()
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
), patch("bookwyrm.lists_stream.add_list_task.delay"):
|
||||
models.List.objects.create(name="Public list", user=self.local_user)
|
||||
models.List.objects.create(
|
||||
name="Private list", privacy="direct", user=self.local_user
|
||||
)
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
result = view(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
request.user = self.anonymous_user
|
||||
|
||||
result = view(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_saved_lists_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.SavedLists.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
booklist = models.List.objects.create(
|
||||
name="Public list", user=self.local_user
|
||||
)
|
||||
models.List.objects.create(
|
||||
name="Private list", privacy="direct", user=self.local_user
|
||||
)
|
||||
self.local_user.saved_lists.add(booklist)
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
result = view(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
self.assertEqual(result.context_data["lists"].object_list, [booklist])
|
||||
|
||||
def test_saved_lists_page_empty(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.SavedLists.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.List.objects.create(name="Public list", user=self.local_user)
|
||||
models.List.objects.create(
|
||||
name="Private list", privacy="direct", user=self.local_user
|
||||
)
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
result = view(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
self.assertEqual(len(result.context_data["lists"].object_list), 0)
|
||||
|
||||
def test_saved_lists_page_logged_out(self):
|
||||
"""logged out saved lists"""
|
||||
view = views.SavedLists.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.anonymous_user
|
||||
|
||||
result = view(request)
|
||||
self.assertEqual(result.status_code, 302)
|
||||
|
||||
def test_user_lists_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.UserLists.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.List.objects.create(name="Public list", user=self.local_user)
|
||||
models.List.objects.create(
|
||||
name="Private list", privacy="direct", user=self.local_user
|
||||
)
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
result = view(request, self.local_user.localname)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_user_lists_page_logged_out(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.UserLists.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.anonymous_user
|
||||
|
||||
result = view(request, self.local_user.username)
|
||||
self.assertEqual(result.status_code, 302)
|
||||
|
||||
def test_lists_create(self):
|
||||
"""create list view"""
|
||||
view = views.Lists.as_view()
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"name": "A list",
|
||||
"description": "wow",
|
||||
"privacy": "unlisted",
|
||||
"curation": "open",
|
||||
"user": self.local_user.id,
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
result = view(request)
|
||||
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Create")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
|
||||
self.assertEqual(result.status_code, 302)
|
||||
new_list = models.List.objects.filter(name="A list").get()
|
||||
self.assertEqual(new_list.description, "wow")
|
||||
self.assertEqual(new_list.privacy, "unlisted")
|
||||
self.assertEqual(new_list.curation, "open")
|
|
@ -1,438 +0,0 @@
|
|||
""" test for app action functionality """
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.http.response import Http404
|
||||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from bookwyrm import models, views
|
||||
from bookwyrm.activitypub import ActivitypubResponse
|
||||
from bookwyrm.tests.validate_html import validate_html
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
class ListViews(TestCase):
|
||||
"""tag views"""
|
||||
|
||||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||
), patch("bookwyrm.lists_stream.populate_lists_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.com",
|
||||
"mouseword",
|
||||
local=True,
|
||||
localname="mouse",
|
||||
remote_id="https://example.com/users/mouse",
|
||||
)
|
||||
self.rat = models.User.objects.create_user(
|
||||
"rat@local.com",
|
||||
"rat@rat.com",
|
||||
"ratword",
|
||||
local=True,
|
||||
localname="rat",
|
||||
remote_id="https://example.com/users/rat",
|
||||
)
|
||||
work = models.Work.objects.create(title="Work")
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Example Edition",
|
||||
remote_id="https://example.com/book/1",
|
||||
parent_work=work,
|
||||
)
|
||||
work_two = models.Work.objects.create(title="Labori")
|
||||
self.book_two = models.Edition.objects.create(
|
||||
title="Example Edition 2",
|
||||
remote_id="https://example.com/book/2",
|
||||
parent_work=work_two,
|
||||
)
|
||||
work_three = models.Work.objects.create(title="Trabajar")
|
||||
self.book_three = models.Edition.objects.create(
|
||||
title="Example Edition 3",
|
||||
remote_id="https://example.com/book/3",
|
||||
parent_work=work_three,
|
||||
)
|
||||
work_four = models.Work.objects.create(title="Travailler")
|
||||
self.book_four = models.Edition.objects.create(
|
||||
title="Example Edition 4",
|
||||
remote_id="https://example.com/book/4",
|
||||
parent_work=work_four,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
self.list = models.List.objects.create(
|
||||
name="Test List", user=self.local_user
|
||||
)
|
||||
self.anonymous_user = AnonymousUser
|
||||
self.anonymous_user.is_authenticated = False
|
||||
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
@patch("bookwyrm.lists_stream.ListsStream.get_list_stream")
|
||||
def test_lists_page(self, _):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.Lists.as_view()
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
), patch("bookwyrm.lists_stream.add_list_task.delay"):
|
||||
models.List.objects.create(name="Public list", user=self.local_user)
|
||||
models.List.objects.create(
|
||||
name="Private list", privacy="direct", user=self.local_user
|
||||
)
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
result = view(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
request.user = self.anonymous_user
|
||||
|
||||
result = view(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_saved_lists_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.SavedLists.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
booklist = models.List.objects.create(
|
||||
name="Public list", user=self.local_user
|
||||
)
|
||||
models.List.objects.create(
|
||||
name="Private list", privacy="direct", user=self.local_user
|
||||
)
|
||||
self.local_user.saved_lists.add(booklist)
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
result = view(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
self.assertEqual(result.context_data["lists"].object_list, [booklist])
|
||||
|
||||
def test_saved_lists_page_empty(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.SavedLists.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.List.objects.create(name="Public list", user=self.local_user)
|
||||
models.List.objects.create(
|
||||
name="Private list", privacy="direct", user=self.local_user
|
||||
)
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
result = view(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
self.assertEqual(len(result.context_data["lists"].object_list), 0)
|
||||
|
||||
def test_saved_lists_page_logged_out(self):
|
||||
"""logged out saved lists"""
|
||||
view = views.SavedLists.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.anonymous_user
|
||||
|
||||
result = view(request)
|
||||
self.assertEqual(result.status_code, 302)
|
||||
|
||||
def test_lists_create(self):
|
||||
"""create list view"""
|
||||
view = views.Lists.as_view()
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"name": "A list",
|
||||
"description": "wow",
|
||||
"privacy": "unlisted",
|
||||
"curation": "open",
|
||||
"user": self.local_user.id,
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
result = view(request)
|
||||
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Create")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
|
||||
self.assertEqual(result.status_code, 302)
|
||||
new_list = models.List.objects.filter(name="A list").get()
|
||||
self.assertEqual(new_list.description, "wow")
|
||||
self.assertEqual(new_list.privacy, "unlisted")
|
||||
self.assertEqual(new_list.curation, "open")
|
||||
|
||||
def test_list_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_page_sorted(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
for (i, book) in enumerate([self.book, self.book_two, self.book_three]):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=book,
|
||||
approved=True,
|
||||
order=i + 1,
|
||||
)
|
||||
|
||||
request = self.factory.get("/?sort_by=order")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
request = self.factory.get("/?sort_by=title")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
request = self.factory.get("/?sort_by=rating")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
request = self.factory.get("/?sort_by=sdkfh")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_page_empty(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_page_logged_out(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
|
||||
request = self.factory.get("")
|
||||
request.user = self.anonymous_user
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_page_json_view(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = True
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_page_json_view_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
request = self.factory.get("/?page=1")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = True
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_edit(self):
|
||||
"""edit a list"""
|
||||
view = views.List.as_view()
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"name": "New Name",
|
||||
"description": "wow",
|
||||
"privacy": "direct",
|
||||
"curation": "curated",
|
||||
"user": self.local_user.id,
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
result = view(request, self.list.id)
|
||||
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Update")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["object"]["id"], self.list.remote_id)
|
||||
|
||||
self.assertEqual(result.status_code, 302)
|
||||
|
||||
self.list.refresh_from_db()
|
||||
self.assertEqual(self.list.name, "New Name")
|
||||
self.assertEqual(self.list.description, "wow")
|
||||
self.assertEqual(self.list.privacy, "direct")
|
||||
self.assertEqual(self.list.curation, "curated")
|
||||
|
||||
def test_curate_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.Curate.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=False,
|
||||
order=1,
|
||||
)
|
||||
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
request.user = self.anonymous_user
|
||||
result = view(request, self.list.id)
|
||||
self.assertEqual(result.status_code, 302)
|
||||
|
||||
def test_user_lists_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.UserLists.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.List.objects.create(name="Public list", user=self.local_user)
|
||||
models.List.objects.create(
|
||||
name="Private list", privacy="direct", user=self.local_user
|
||||
)
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
result = view(request, self.local_user.localname)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_user_lists_page_logged_out(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.UserLists.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.anonymous_user
|
||||
|
||||
result = view(request, self.local_user.username)
|
||||
self.assertEqual(result.status_code, 302)
|
||||
|
||||
def test_embed_call_without_key(self):
|
||||
"""there are so many views, this just makes sure it DOESN’T load"""
|
||||
view = views.unsafe_embed_list
|
||||
request = self.factory.get("")
|
||||
request.user = self.anonymous_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
with self.assertRaises(Http404):
|
||||
view(request, self.list.id, "")
|
||||
|
||||
def test_embed_call_with_key(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.unsafe_embed_list
|
||||
request = self.factory.get("")
|
||||
request.user = self.anonymous_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
|
||||
embed_key = str(self.list.embed_key.hex)
|
||||
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id, embed_key)
|
||||
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
|
@ -357,16 +357,21 @@ urlpatterns = [
|
|||
re_path(r"^list/?$", views.Lists.as_view(), name="lists"),
|
||||
re_path(r"^list/saved/?$", views.SavedLists.as_view(), name="saved-lists"),
|
||||
re_path(r"^list/(?P<list_id>\d+)(.json)?/?$", views.List.as_view(), name="list"),
|
||||
re_path(
|
||||
r"^list/(?P<list_id>\d+)/item/(?P<list_item>\d+)/?$",
|
||||
views.ListItem.as_view(),
|
||||
name="list-item",
|
||||
),
|
||||
re_path(r"^list/delete/(?P<list_id>\d+)/?$", views.delete_list, name="delete-list"),
|
||||
re_path(r"^list/add-book/?$", views.list.add_book, name="list-add-book"),
|
||||
re_path(r"^list/add-book/?$", views.add_book, name="list-add-book"),
|
||||
re_path(
|
||||
r"^list/(?P<list_id>\d+)/remove/?$",
|
||||
views.list.remove_book,
|
||||
views.remove_book,
|
||||
name="list-remove-book",
|
||||
),
|
||||
re_path(
|
||||
r"^list-item/(?P<list_item_id>\d+)/set-position$",
|
||||
views.list.set_book_position,
|
||||
views.set_book_position,
|
||||
name="list-set-book-position",
|
||||
),
|
||||
re_path(
|
||||
|
|
|
@ -61,6 +61,21 @@ from .imports.manually_review import (
|
|||
delete_import_item,
|
||||
)
|
||||
|
||||
# lists
|
||||
from .list.curate import Curate
|
||||
from .list.embed import unsafe_embed_list
|
||||
from .list.list_item import ListItem
|
||||
from .list.lists import Lists, SavedLists, UserLists
|
||||
from .list.list import (
|
||||
List,
|
||||
save_list,
|
||||
unsave_list,
|
||||
delete_list,
|
||||
add_book,
|
||||
remove_book,
|
||||
set_book_position,
|
||||
)
|
||||
|
||||
# misc views
|
||||
from .author import Author, EditAuthor, update_author_from_remote
|
||||
from .directory import Directory
|
||||
|
@ -90,8 +105,6 @@ from .group import (
|
|||
from .inbox import Inbox
|
||||
from .interaction import Favorite, Unfavorite, Boost, Unboost
|
||||
from .isbn import Isbn
|
||||
from .list import Lists, SavedLists, List, Curate, UserLists
|
||||
from .list import save_list, unsave_list, delete_list, unsafe_embed_list
|
||||
from .notifications import Notifications
|
||||
from .outbox import Outbox
|
||||
from .reading import ReadThrough, delete_readthrough, delete_progressupdate
|
||||
|
|
55
bookwyrm/views/list/curate.py
Normal file
55
bookwyrm/views/list/curate.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
""" book list views"""
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Max
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
|
||||
from bookwyrm import forms, models
|
||||
from bookwyrm.views.list.list import increment_order_in_reverse
|
||||
from bookwyrm.views.list.list import normalize_book_list_ordering
|
||||
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class Curate(View):
|
||||
"""approve or discard list suggestsions"""
|
||||
|
||||
def get(self, request, list_id):
|
||||
"""display a pending list"""
|
||||
book_list = get_object_or_404(models.List, id=list_id)
|
||||
book_list.raise_not_editable(request.user)
|
||||
|
||||
data = {
|
||||
"list": book_list,
|
||||
"pending": book_list.listitem_set.filter(approved=False),
|
||||
"list_form": forms.ListForm(instance=book_list),
|
||||
}
|
||||
return TemplateResponse(request, "lists/curate.html", data)
|
||||
|
||||
def post(self, request, list_id):
|
||||
"""edit a book_list"""
|
||||
book_list = get_object_or_404(models.List, id=list_id)
|
||||
book_list.raise_not_editable(request.user)
|
||||
|
||||
suggestion = get_object_or_404(models.ListItem, id=request.POST.get("item"))
|
||||
approved = request.POST.get("approved") == "true"
|
||||
if approved:
|
||||
# update the book and set it to be the last in the order of approved books,
|
||||
# before any pending books
|
||||
suggestion.approved = True
|
||||
order_max = (
|
||||
book_list.listitem_set.filter(approved=True).aggregate(Max("order"))[
|
||||
"order__max"
|
||||
]
|
||||
or 0
|
||||
) + 1
|
||||
suggestion.order = order_max
|
||||
increment_order_in_reverse(book_list.id, order_max)
|
||||
suggestion.save()
|
||||
else:
|
||||
deleted_order = suggestion.order
|
||||
suggestion.delete(broadcast=False)
|
||||
normalize_book_list_ordering(book_list.id, start=deleted_order)
|
||||
return redirect("list-curate", book_list.id)
|
75
bookwyrm/views/list/embed.py
Normal file
75
bookwyrm/views/list/embed.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
""" book list views"""
|
||||
from django.core.paginator import Paginator
|
||||
from django.db.models import Avg, DecimalField
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template.response import TemplateResponse
|
||||
from django.views import View
|
||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
|
||||
from bookwyrm import models
|
||||
from bookwyrm.settings import PAGE_LENGTH
|
||||
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
class EmbedList(View):
|
||||
"""embeded book list page"""
|
||||
|
||||
def get(self, request, list_id, list_key):
|
||||
"""display a book list"""
|
||||
book_list = get_object_or_404(models.List, id=list_id)
|
||||
|
||||
embed_key = str(book_list.embed_key.hex)
|
||||
|
||||
if list_key != embed_key:
|
||||
raise Http404()
|
||||
|
||||
# sort_by shall be "order" unless a valid alternative is given
|
||||
sort_by = request.GET.get("sort_by", "order")
|
||||
if sort_by not in ("order", "title", "rating"):
|
||||
sort_by = "order"
|
||||
|
||||
# direction shall be "ascending" unless a valid alternative is given
|
||||
direction = request.GET.get("direction", "ascending")
|
||||
if direction not in ("ascending", "descending"):
|
||||
direction = "ascending"
|
||||
|
||||
directional_sort_by = {
|
||||
"order": "order",
|
||||
"title": "book__title",
|
||||
"rating": "average_rating",
|
||||
}[sort_by]
|
||||
if direction == "descending":
|
||||
directional_sort_by = "-" + directional_sort_by
|
||||
|
||||
items = book_list.listitem_set.prefetch_related("user", "book", "book__authors")
|
||||
if sort_by == "rating":
|
||||
items = items.annotate(
|
||||
average_rating=Avg(
|
||||
Coalesce("book__review__rating", 0.0),
|
||||
output_field=DecimalField(),
|
||||
)
|
||||
)
|
||||
items = items.filter(approved=True).order_by(directional_sort_by)
|
||||
|
||||
paginated = Paginator(items, PAGE_LENGTH)
|
||||
|
||||
page = paginated.get_page(request.GET.get("page"))
|
||||
|
||||
data = {
|
||||
"list": book_list,
|
||||
"items": page,
|
||||
"page_range": paginated.get_elided_page_range(
|
||||
page.number, on_each_side=2, on_ends=1
|
||||
),
|
||||
}
|
||||
return TemplateResponse(request, "lists/embed-list.html", data)
|
||||
|
||||
|
||||
@xframe_options_exempt
|
||||
def unsafe_embed_list(request, *args, **kwargs):
|
||||
"""allows the EmbedList view to be loaded through unsafe iframe origins"""
|
||||
|
||||
embed_list_view = EmbedList.as_view()
|
||||
return embed_list_view(request, *args, **kwargs)
|
|
@ -3,96 +3,26 @@ from typing import Optional
|
|||
from urllib.parse import urlencode
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.paginator import Paginator
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db.models import Avg, DecimalField, Q, Max
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.http import HttpResponseBadRequest, HttpResponse, Http404
|
||||
from django.http import HttpResponseBadRequest, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from django.views.decorators.http import require_POST
|
||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
|
||||
from bookwyrm import book_search, forms, models
|
||||
from bookwyrm.activitypub import ActivitypubResponse
|
||||
from bookwyrm.lists_stream import ListsStream
|
||||
from bookwyrm.settings import PAGE_LENGTH
|
||||
from .helpers import is_api_request
|
||||
from .helpers import get_user_from_username
|
||||
from bookwyrm.views.helpers import is_api_request
|
||||
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
class Lists(View):
|
||||
"""book list page"""
|
||||
|
||||
def get(self, request):
|
||||
"""display a book list"""
|
||||
lists = ListsStream().get_list_stream(request.user)
|
||||
paginated = Paginator(lists, 12)
|
||||
data = {
|
||||
"lists": paginated.get_page(request.GET.get("page")),
|
||||
"list_form": forms.ListForm(),
|
||||
"path": "/list",
|
||||
}
|
||||
return TemplateResponse(request, "lists/lists.html", data)
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
# pylint: disable=unused-argument
|
||||
def post(self, request):
|
||||
"""create a book_list"""
|
||||
form = forms.ListForm(request.POST)
|
||||
if not form.is_valid():
|
||||
return redirect("lists")
|
||||
book_list = form.save()
|
||||
# list should not have a group if it is not group curated
|
||||
if not book_list.curation == "group":
|
||||
book_list.group = None
|
||||
book_list.save(broadcast=False)
|
||||
|
||||
return redirect(book_list.local_path)
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class SavedLists(View):
|
||||
"""saved book list page"""
|
||||
|
||||
def get(self, request):
|
||||
"""display book lists"""
|
||||
# hide lists with no approved books
|
||||
lists = request.user.saved_lists.order_by("-updated_date")
|
||||
|
||||
paginated = Paginator(lists, 12)
|
||||
data = {
|
||||
"lists": paginated.get_page(request.GET.get("page")),
|
||||
"list_form": forms.ListForm(),
|
||||
"path": "/list",
|
||||
}
|
||||
return TemplateResponse(request, "lists/lists.html", data)
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class UserLists(View):
|
||||
"""a user's book list page"""
|
||||
|
||||
def get(self, request, username):
|
||||
"""display a book list"""
|
||||
user = get_user_from_username(request.user, username)
|
||||
lists = models.List.privacy_filter(request.user).filter(user=user)
|
||||
paginated = Paginator(lists, 12)
|
||||
|
||||
data = {
|
||||
"user": user,
|
||||
"is_self": request.user.id == user.id,
|
||||
"lists": paginated.get_page(request.GET.get("page")),
|
||||
"list_form": forms.ListForm(),
|
||||
"path": user.local_path + "/lists",
|
||||
}
|
||||
return TemplateResponse(request, "user/lists.html", data)
|
||||
|
||||
|
||||
class List(View):
|
||||
"""book list page"""
|
||||
|
||||
|
@ -191,7 +121,8 @@ class List(View):
|
|||
|
||||
form = forms.ListForm(request.POST, instance=book_list)
|
||||
if not form.is_valid():
|
||||
return redirect("list", book_list.id)
|
||||
# this shouldn't happen
|
||||
raise Exception(form.errors)
|
||||
book_list = form.save()
|
||||
if not book_list.curation == "group":
|
||||
book_list.group = None
|
||||
|
@ -200,103 +131,6 @@ class List(View):
|
|||
return redirect(book_list.local_path)
|
||||
|
||||
|
||||
class EmbedList(View):
|
||||
"""embeded book list page"""
|
||||
|
||||
def get(self, request, list_id, list_key):
|
||||
"""display a book list"""
|
||||
book_list = get_object_or_404(models.List, id=list_id)
|
||||
|
||||
embed_key = str(book_list.embed_key.hex)
|
||||
|
||||
if list_key != embed_key:
|
||||
raise Http404()
|
||||
|
||||
# sort_by shall be "order" unless a valid alternative is given
|
||||
sort_by = request.GET.get("sort_by", "order")
|
||||
if sort_by not in ("order", "title", "rating"):
|
||||
sort_by = "order"
|
||||
|
||||
# direction shall be "ascending" unless a valid alternative is given
|
||||
direction = request.GET.get("direction", "ascending")
|
||||
if direction not in ("ascending", "descending"):
|
||||
direction = "ascending"
|
||||
|
||||
directional_sort_by = {
|
||||
"order": "order",
|
||||
"title": "book__title",
|
||||
"rating": "average_rating",
|
||||
}[sort_by]
|
||||
if direction == "descending":
|
||||
directional_sort_by = "-" + directional_sort_by
|
||||
|
||||
items = book_list.listitem_set.prefetch_related("user", "book", "book__authors")
|
||||
if sort_by == "rating":
|
||||
items = items.annotate(
|
||||
average_rating=Avg(
|
||||
Coalesce("book__review__rating", 0.0),
|
||||
output_field=DecimalField(),
|
||||
)
|
||||
)
|
||||
items = items.filter(approved=True).order_by(directional_sort_by)
|
||||
|
||||
paginated = Paginator(items, PAGE_LENGTH)
|
||||
|
||||
page = paginated.get_page(request.GET.get("page"))
|
||||
|
||||
data = {
|
||||
"list": book_list,
|
||||
"items": page,
|
||||
"page_range": paginated.get_elided_page_range(
|
||||
page.number, on_each_side=2, on_ends=1
|
||||
),
|
||||
}
|
||||
return TemplateResponse(request, "lists/embed-list.html", data)
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class Curate(View):
|
||||
"""approve or discard list suggestsions"""
|
||||
|
||||
def get(self, request, list_id):
|
||||
"""display a pending list"""
|
||||
book_list = get_object_or_404(models.List, id=list_id)
|
||||
book_list.raise_not_editable(request.user)
|
||||
|
||||
data = {
|
||||
"list": book_list,
|
||||
"pending": book_list.listitem_set.filter(approved=False),
|
||||
"list_form": forms.ListForm(instance=book_list),
|
||||
}
|
||||
return TemplateResponse(request, "lists/curate.html", data)
|
||||
|
||||
def post(self, request, list_id):
|
||||
"""edit a book_list"""
|
||||
book_list = get_object_or_404(models.List, id=list_id)
|
||||
book_list.raise_not_editable(request.user)
|
||||
|
||||
suggestion = get_object_or_404(models.ListItem, id=request.POST.get("item"))
|
||||
approved = request.POST.get("approved") == "true"
|
||||
if approved:
|
||||
# update the book and set it to be the last in the order of approved books,
|
||||
# before any pending books
|
||||
suggestion.approved = True
|
||||
order_max = (
|
||||
book_list.listitem_set.filter(approved=True).aggregate(Max("order"))[
|
||||
"order__max"
|
||||
]
|
||||
or 0
|
||||
) + 1
|
||||
suggestion.order = order_max
|
||||
increment_order_in_reverse(book_list.id, order_max)
|
||||
suggestion.save()
|
||||
else:
|
||||
deleted_order = suggestion.order
|
||||
suggestion.delete(broadcast=False)
|
||||
normalize_book_list_ordering(book_list.id, start=deleted_order)
|
||||
return redirect("list-curate", book_list.id)
|
||||
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
def save_list(request, list_id):
|
||||
|
@ -330,53 +164,41 @@ def delete_list(request, list_id):
|
|||
|
||||
@require_POST
|
||||
@login_required
|
||||
@transaction.atomic
|
||||
def add_book(request):
|
||||
"""put a book on a list"""
|
||||
book_list = get_object_or_404(models.List, id=request.POST.get("list"))
|
||||
is_group_member = False
|
||||
if book_list.curation == "group":
|
||||
is_group_member = models.GroupMember.objects.filter(
|
||||
group=book_list.group, user=request.user
|
||||
).exists()
|
||||
|
||||
book_list = get_object_or_404(models.List, id=request.POST.get("book_list"))
|
||||
# make sure the user is allowed to submit to this list
|
||||
book_list.raise_visible_to_user(request.user)
|
||||
if request.user != book_list.user and book_list.curation == "closed":
|
||||
raise PermissionDenied()
|
||||
|
||||
is_group_member = models.GroupMember.objects.filter(
|
||||
group=book_list.group, user=request.user
|
||||
).exists()
|
||||
|
||||
form = forms.ListItemForm(request.POST)
|
||||
if not form.is_valid():
|
||||
# this shouldn't happen, there aren't validated fields
|
||||
raise Exception(form.errors)
|
||||
item = form.save(commit=False)
|
||||
|
||||
if book_list.curation == "curated":
|
||||
# make a pending entry at the end of the list
|
||||
order_max = (book_list.listitem_set.aggregate(Max("order"))["order__max"]) or 0
|
||||
item.approved = is_group_member or request.user == book_list.user
|
||||
else:
|
||||
# add the book at the latest order of approved books, before pending books
|
||||
order_max = (
|
||||
book_list.listitem_set.filter(approved=True).aggregate(Max("order"))[
|
||||
"order__max"
|
||||
]
|
||||
) or 0
|
||||
increment_order_in_reverse(book_list.id, order_max + 1)
|
||||
item.order = order_max + 1
|
||||
|
||||
book = get_object_or_404(models.Edition, id=request.POST.get("book"))
|
||||
# do you have permission to add to the list?
|
||||
try:
|
||||
if (
|
||||
request.user == book_list.user
|
||||
or is_group_member
|
||||
or book_list.curation == "open"
|
||||
):
|
||||
# add the book at the latest order of approved books, before pending books
|
||||
order_max = (
|
||||
book_list.listitem_set.filter(approved=True).aggregate(Max("order"))[
|
||||
"order__max"
|
||||
]
|
||||
) or 0
|
||||
increment_order_in_reverse(book_list.id, order_max + 1)
|
||||
models.ListItem.objects.create(
|
||||
book=book,
|
||||
book_list=book_list,
|
||||
user=request.user,
|
||||
order=order_max + 1,
|
||||
)
|
||||
elif book_list.curation == "curated":
|
||||
# make a pending entry at the end of the list
|
||||
order_max = (
|
||||
book_list.listitem_set.aggregate(Max("order"))["order__max"]
|
||||
) or 0
|
||||
models.ListItem.objects.create(
|
||||
approved=False,
|
||||
book=book,
|
||||
book_list=book_list,
|
||||
user=request.user,
|
||||
order=order_max + 1,
|
||||
)
|
||||
else:
|
||||
# you can't add to this list, what were you THINKING
|
||||
return HttpResponseBadRequest()
|
||||
item.save()
|
||||
except IntegrityError:
|
||||
# if the book is already on the list, don't flip out
|
||||
pass
|
||||
|
@ -499,11 +321,3 @@ def normalize_book_list_ordering(book_list_id, start=0, add_offset=0):
|
|||
if item.order != effective_order:
|
||||
item.order = effective_order
|
||||
item.save()
|
||||
|
||||
|
||||
@xframe_options_exempt
|
||||
def unsafe_embed_list(request, *args, **kwargs):
|
||||
"""allows the EmbedList view to be loaded through unsafe iframe origins"""
|
||||
|
||||
embed_list_view = EmbedList.as_view()
|
||||
return embed_list_view(request, *args, **kwargs)
|
22
bookwyrm/views/list/list_item.py
Normal file
22
bookwyrm/views/list/list_item.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
""" book list views"""
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
|
||||
from bookwyrm import forms, models
|
||||
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class ListItem(View):
|
||||
"""book list page"""
|
||||
|
||||
def post(self, request, list_id, list_item):
|
||||
"""Edit a list item's notes"""
|
||||
list_item = get_object_or_404(models.ListItem, id=list_item, book_list=list_id)
|
||||
list_item.raise_not_editable(request.user)
|
||||
form = forms.ListItemForm(request.POST, instance=list_item)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect("list", list_item.book_list.id)
|
80
bookwyrm/views/list/lists.py
Normal file
80
bookwyrm/views/list/lists.py
Normal file
|
@ -0,0 +1,80 @@
|
|||
""" book list views"""
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.paginator import Paginator
|
||||
from django.shortcuts import redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
|
||||
from bookwyrm import forms, models
|
||||
from bookwyrm.lists_stream import ListsStream
|
||||
from bookwyrm.views.helpers import get_user_from_username
|
||||
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
class Lists(View):
|
||||
"""book list page"""
|
||||
|
||||
def get(self, request):
|
||||
"""display a book list"""
|
||||
lists = ListsStream().get_list_stream(request.user)
|
||||
paginated = Paginator(lists, 12)
|
||||
data = {
|
||||
"lists": paginated.get_page(request.GET.get("page")),
|
||||
"list_form": forms.ListForm(),
|
||||
"path": "/list",
|
||||
}
|
||||
return TemplateResponse(request, "lists/lists.html", data)
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
# pylint: disable=unused-argument
|
||||
def post(self, request):
|
||||
"""create a book_list"""
|
||||
form = forms.ListForm(request.POST)
|
||||
if not form.is_valid():
|
||||
return redirect("lists")
|
||||
book_list = form.save()
|
||||
# list should not have a group if it is not group curated
|
||||
if not book_list.curation == "group":
|
||||
book_list.group = None
|
||||
book_list.save(broadcast=False)
|
||||
|
||||
return redirect(book_list.local_path)
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class SavedLists(View):
|
||||
"""saved book list page"""
|
||||
|
||||
def get(self, request):
|
||||
"""display book lists"""
|
||||
# hide lists with no approved books
|
||||
lists = request.user.saved_lists.order_by("-updated_date")
|
||||
|
||||
paginated = Paginator(lists, 12)
|
||||
data = {
|
||||
"lists": paginated.get_page(request.GET.get("page")),
|
||||
"list_form": forms.ListForm(),
|
||||
"path": "/list",
|
||||
}
|
||||
return TemplateResponse(request, "lists/lists.html", data)
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class UserLists(View):
|
||||
"""a user's book list page"""
|
||||
|
||||
def get(self, request, username):
|
||||
"""display a book list"""
|
||||
user = get_user_from_username(request.user, username)
|
||||
lists = models.List.privacy_filter(request.user).filter(user=user)
|
||||
paginated = Paginator(lists, 12)
|
||||
|
||||
data = {
|
||||
"user": user,
|
||||
"is_self": request.user.id == user.id,
|
||||
"lists": paginated.get_page(request.GET.get("page")),
|
||||
"list_form": forms.ListForm(),
|
||||
"path": user.local_path + "/lists",
|
||||
}
|
||||
return TemplateResponse(request, "user/lists.html", data)
|
Loading…
Reference in a new issue