mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-27 12:01:14 +00:00
Merge pull request #1469 from bookwyrm-social/user-shelf
Reorganize user/shelf/goal views code
This commit is contained in:
commit
6dbd402345
19 changed files with 273 additions and 123 deletions
|
@ -268,7 +268,7 @@ class CreateInviteForm(CustomForm):
|
||||||
class ShelfForm(CustomForm):
|
class ShelfForm(CustomForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Shelf
|
model = models.Shelf
|
||||||
fields = ["user", "name", "privacy"]
|
fields = ["user", "name", "privacy", "description"]
|
||||||
|
|
||||||
|
|
||||||
class GoalForm(CustomForm):
|
class GoalForm(CustomForm):
|
||||||
|
|
18
bookwyrm/migrations/0100_shelf_description.py
Normal file
18
bookwyrm/migrations/0100_shelf_description.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.5 on 2021-09-28 23:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0099_readthrough_is_active"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="shelf",
|
||||||
|
name="description",
|
||||||
|
field=models.TextField(blank=True, max_length=500, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -21,6 +21,7 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel):
|
||||||
|
|
||||||
name = fields.CharField(max_length=100)
|
name = fields.CharField(max_length=100)
|
||||||
identifier = models.CharField(max_length=100)
|
identifier = models.CharField(max_length=100)
|
||||||
|
description = models.TextField(blank=True, null=True, max_length=500)
|
||||||
user = fields.ForeignKey(
|
user = fields.ForeignKey(
|
||||||
"User", on_delete=models.PROTECT, activitypub_field="owner"
|
"User", on_delete=models.PROTECT, activitypub_field="owner"
|
||||||
)
|
)
|
||||||
|
@ -52,6 +53,11 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel):
|
||||||
"""list of books for this shelf, overrides OrderedCollectionMixin"""
|
"""list of books for this shelf, overrides OrderedCollectionMixin"""
|
||||||
return self.books.order_by("shelfbook")
|
return self.books.order_by("shelfbook")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def deletable(self):
|
||||||
|
"""can the shelf be safely deleted?"""
|
||||||
|
return self.editable and not self.shelfbook_set.exists()
|
||||||
|
|
||||||
def get_remote_id(self):
|
def get_remote_id(self):
|
||||||
"""shelf identifier instead of id"""
|
"""shelf identifier instead of id"""
|
||||||
base_path = self.user.remote_id
|
base_path = self.user.remote_id
|
||||||
|
@ -61,7 +67,7 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel):
|
||||||
def raise_not_deletable(self, viewer):
|
def raise_not_deletable(self, viewer):
|
||||||
"""don't let anyone delete a default shelf"""
|
"""don't let anyone delete a default shelf"""
|
||||||
super().raise_not_deletable(viewer)
|
super().raise_not_deletable(viewer)
|
||||||
if not self.editable:
|
if not self.deletable:
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
13
bookwyrm/templates/shelf/create_shelf_form.html
Normal file
13
bookwyrm/templates/shelf/create_shelf_form.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends 'components/inline_form.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
{% trans "Create Shelf" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block form %}
|
||||||
|
<form name="create-shelf" action="{% url 'shelf-create' %}" method="post">
|
||||||
|
{% include "shelf/form.html" with editable=shelf.editable form=create_form %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
13
bookwyrm/templates/shelf/edit_shelf_form.html
Normal file
13
bookwyrm/templates/shelf/edit_shelf_form.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends 'components/inline_form.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
{% trans "Edit Shelf" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block form %}
|
||||||
|
<form name="edit-shelf" action="{{ shelf.local_path }}" method="post">
|
||||||
|
{% include "shelf/form.html" with editable=shelf.editable form=edit_form privacy=shelf.privacy %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
28
bookwyrm/templates/shelf/form.html
Normal file
28
bookwyrm/templates/shelf/form.html
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{% load i18n %}
|
||||||
|
{% load utilities %}
|
||||||
|
{% with 0|uuid as uuid %}
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="user" value="{{ request.user.id }}">
|
||||||
|
|
||||||
|
{% if editable %}
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="id_name">{% trans "Name:" %}</label>
|
||||||
|
<input type="text" name="name" value="{{ form.name.value|default:'' }}" maxlength="100" class="input" required="" id="id_name">
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<input type="hidden" name="name" required="true" value="{{ shelf.name }}">
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="id_description_{{ uuid }}">{% trans "Description:" %}</label>
|
||||||
|
<textarea name="description" cols="40" rows="5" maxlength="500" class="textarea" id="id_description_{{ uuid }}">{{ form.description.value|default:'' }}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="field has-addons">
|
||||||
|
<div class="control">
|
||||||
|
{% include 'snippets/privacy_select.html' with current=privacy %}
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<button class="button is-primary" type="submit">{% trans "Save" %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
|
@ -5,7 +5,7 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% include 'user/shelf/books_header.html' %}
|
{% include 'user/books_header.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block opengraph_images %}
|
{% block opengraph_images %}
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<header class="block">
|
<header class="block">
|
||||||
<h1 class="title">
|
<h1 class="title">
|
||||||
{% include 'user/shelf/books_header.html' %}
|
{% include 'user/books_header.html' %}
|
||||||
</h1>
|
</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -60,45 +60,62 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{% include 'user/shelf/create_shelf_form.html' with controls_text='create_shelf_form' %}
|
{% include 'shelf/create_shelf_form.html' with controls_text='create_shelf_form' %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block columns is-mobile">
|
<div>
|
||||||
<div class="column">
|
<div class="block columns is-mobile">
|
||||||
<h2 class="title is-3">
|
<div class="column">
|
||||||
{{ shelf.name }}
|
<h2 class="title is-3">
|
||||||
<span class="subtitle">
|
{{ shelf.name }}
|
||||||
{% include 'snippets/privacy-icons.html' with item=shelf %}
|
<span class="subtitle">
|
||||||
</span>
|
{% include 'snippets/privacy-icons.html' with item=shelf %}
|
||||||
{% with count=books.paginator.count %}
|
</span>
|
||||||
{% if count %}
|
{% with count=books.paginator.count %}
|
||||||
<p class="help">
|
{% if count %}
|
||||||
{% blocktrans trimmed count counter=count with formatted_count=count|intcomma %}
|
<p class="help">
|
||||||
{{ formatted_count }} book
|
{% blocktrans trimmed count counter=count with formatted_count=count|intcomma %}
|
||||||
{% plural %}
|
{{ formatted_count }} book
|
||||||
{{ formatted_count }} books
|
{% plural %}
|
||||||
{% endblocktrans %}
|
{{ formatted_count }} books
|
||||||
|
|
||||||
{% if books.has_other_pages %}
|
|
||||||
{% blocktrans trimmed with start=books.start_index end=books.end_index %}
|
|
||||||
(showing {{ start }}-{{ end }})
|
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
|
|
||||||
|
{% if books.has_other_pages %}
|
||||||
|
{% blocktrans trimmed with start=books.start_index end=books.end_index %}
|
||||||
|
(showing {{ start }}-{{ end }})
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
{% endwith %}
|
||||||
{% endif %}
|
</h2>
|
||||||
{% endwith %}
|
</div>
|
||||||
</h2>
|
{% if is_self and shelf.id %}
|
||||||
</div>
|
<div class="column is-narrow">
|
||||||
{% if is_self and shelf.id %}
|
<div class="is-flex">
|
||||||
<div class="column is-narrow">
|
{% trans "Edit shelf" as button_text %}
|
||||||
{% trans "Edit shelf" as button_text %}
|
{% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="pencil" controls_text="edit_shelf_form" focus="edit_shelf_form_header" %}
|
||||||
{% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="pencil" controls_text="edit_shelf_form" focus="edit_shelf_form_header" %}
|
|
||||||
|
{% if shelf.deletable %}
|
||||||
|
<form class="ml-1" name="delete-shelf" action="/delete-shelf/{{ shelf.id }}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="user" value="{{ request.user.id }}">
|
||||||
|
<button class="button is-danger is-light" type="submit">
|
||||||
|
{% trans "Delete shelf" %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if shelf.description %}
|
||||||
|
<p>{{ shelf.description }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{% include 'user/shelf/edit_shelf_form.html' with controls_text="edit_shelf_form" %}
|
{% include 'shelf/edit_shelf_form.html' with controls_text="edit_shelf_form" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
|
@ -167,17 +184,7 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{% trans "This shelf is empty." %}</p>
|
<p><em>{% trans "This shelf is empty." %}</em></p>
|
||||||
{% if shelf.id and shelf.editable %}
|
|
||||||
<form name="delete-shelf" action="/delete-shelf/{{ shelf.id }}" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="user" value="{{ request.user.id }}">
|
|
||||||
<button class="button is-danger is-light" type="submit">
|
|
||||||
{% trans "Delete shelf" %}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
|
@ -1,7 +1,7 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
<div class="select {{ class }}">
|
<div class="select {{ class }}">
|
||||||
{% with 0|uuid as uuid %}
|
{% firstof uuid 0|uuid as uuid %}
|
||||||
{% if not no_label %}
|
{% if not no_label %}
|
||||||
<label class="is-sr-only" for="privacy_{{ uuid }}">{% trans "Post privacy" %}</label>
|
<label class="is-sr-only" for="privacy_{{ uuid }}">{% trans "Post privacy" %}</label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -20,6 +20,5 @@
|
||||||
{% trans "Private" %}
|
{% trans "Private" %}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
{% endwith %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,6 @@
|
||||||
|
|
||||||
{% block nullstate %}
|
{% block nullstate %}
|
||||||
<div>
|
<div>
|
||||||
{% blocktrans with username=user.display_name %}{{ username }} has no followers{% endblocktrans %}
|
<em>{% blocktrans with username=user.display_name %}{{ username }} has no followers{% endblocktrans %}</em>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
{% block nullstate %}
|
{% block nullstate %}
|
||||||
<div>
|
<div>
|
||||||
{% blocktrans with username=user.display_name %}{{ username }} isn't following any users{% endblocktrans %}
|
<em>{% blocktrans with username=user.display_name %}{{ username }} isn't following any users{% endblocktrans %}</em>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
{% extends 'components/inline_form.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block header %}
|
|
||||||
{% trans "Create Shelf" %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
<form name="create-shelf" action="{% url 'shelf-create' %}" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="user" value="{{ request.user.id }}">
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="id_name_create">{% trans "Name:" %}</label>
|
|
||||||
<input type="text" name="name" maxlength="100" class="input" required="true" id="id_name_create">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field has-addons">
|
|
||||||
<div class="control">
|
|
||||||
{% include 'snippets/privacy_select.html' %}
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<button class="button is-primary" type="submit">{% trans "Create Shelf" %}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
{% extends 'components/inline_form.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block header %}
|
|
||||||
{% trans "Edit Shelf" %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
<form name="edit-shelf" action="{{ shelf.local_path }}" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="user" value="{{ request.user.id }}">
|
|
||||||
{% if shelf.editable %}
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="id_name">{% trans "Name:" %}</label>
|
|
||||||
<input type="text" name="name" maxlength="100" class="input" required="true" value="{{ shelf.name }}" id="id_name">
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<input type="hidden" name="name" required="true" value="{{ shelf.name }}">
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="field has-addons">
|
|
||||||
<div class="control">
|
|
||||||
{% include 'snippets/privacy_select.html' with current=shelf.privacy %}
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<button class="button is-primary" type="submit">{% trans "Update shelf" %}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
{% if user.bookwyrm_user %}
|
{% if user.bookwyrm_user %}
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h2 class="title">
|
<h2 class="title">
|
||||||
{% include 'user/shelf/books_header.html' %}
|
{% include 'user/books_header.html' %}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="columns is-mobile scroll-x">
|
<div class="columns is-mobile scroll-x">
|
||||||
{% for shelf in shelves %}
|
{% for shelf in shelves %}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
""" test for app action functionality """
|
""" test for app action functionality """
|
||||||
import json
|
import json
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
from tidylib import tidy_document
|
||||||
|
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
|
||||||
from bookwyrm import models, views
|
from bookwyrm import forms, models, views
|
||||||
from bookwyrm.activitypub import ActivitypubResponse
|
from bookwyrm.activitypub import ActivitypubResponse
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,7 +56,16 @@ class ShelfViews(TestCase):
|
||||||
is_api.return_value = False
|
is_api.return_value = False
|
||||||
result = view(request, self.local_user.username, shelf.identifier)
|
result = view(request, self.local_user.username, shelf.identifier)
|
||||||
self.assertIsInstance(result, TemplateResponse)
|
self.assertIsInstance(result, TemplateResponse)
|
||||||
result.render()
|
html = result.render()
|
||||||
|
_, errors = tidy_document(
|
||||||
|
html.content,
|
||||||
|
options={
|
||||||
|
"drop-empty-elements": False,
|
||||||
|
"warn-proprietary-attributes": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if errors:
|
||||||
|
raise Exception(errors)
|
||||||
self.assertEqual(result.status_code, 200)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
with patch("bookwyrm.views.shelf.is_api_request") as is_api:
|
with patch("bookwyrm.views.shelf.is_api_request") as is_api:
|
||||||
|
@ -122,7 +134,7 @@ class ShelfViews(TestCase):
|
||||||
|
|
||||||
self.assertEqual(shelf.name, "To Read")
|
self.assertEqual(shelf.name, "To Read")
|
||||||
|
|
||||||
def test_handle_shelve(self, *_):
|
def test_shelve(self, *_):
|
||||||
"""shelve a book"""
|
"""shelve a book"""
|
||||||
request = self.factory.post(
|
request = self.factory.post(
|
||||||
"", {"book": self.book.id, "shelf": self.shelf.identifier}
|
"", {"book": self.book.id, "shelf": self.shelf.identifier}
|
||||||
|
@ -140,7 +152,7 @@ class ShelfViews(TestCase):
|
||||||
# make sure the book is on the shelf
|
# make sure the book is on the shelf
|
||||||
self.assertEqual(self.shelf.books.get(), self.book)
|
self.assertEqual(self.shelf.books.get(), self.book)
|
||||||
|
|
||||||
def test_handle_shelve_to_read(self, *_):
|
def test_shelve_to_read(self, *_):
|
||||||
"""special behavior for the to-read shelf"""
|
"""special behavior for the to-read shelf"""
|
||||||
shelf = models.Shelf.objects.get(identifier="to-read")
|
shelf = models.Shelf.objects.get(identifier="to-read")
|
||||||
request = self.factory.post(
|
request = self.factory.post(
|
||||||
|
@ -153,7 +165,7 @@ class ShelfViews(TestCase):
|
||||||
# make sure the book is on the shelf
|
# make sure the book is on the shelf
|
||||||
self.assertEqual(shelf.books.get(), self.book)
|
self.assertEqual(shelf.books.get(), self.book)
|
||||||
|
|
||||||
def test_handle_shelve_reading(self, *_):
|
def test_shelve_reading(self, *_):
|
||||||
"""special behavior for the reading shelf"""
|
"""special behavior for the reading shelf"""
|
||||||
shelf = models.Shelf.objects.get(identifier="reading")
|
shelf = models.Shelf.objects.get(identifier="reading")
|
||||||
request = self.factory.post(
|
request = self.factory.post(
|
||||||
|
@ -166,7 +178,7 @@ class ShelfViews(TestCase):
|
||||||
# make sure the book is on the shelf
|
# make sure the book is on the shelf
|
||||||
self.assertEqual(shelf.books.get(), self.book)
|
self.assertEqual(shelf.books.get(), self.book)
|
||||||
|
|
||||||
def test_handle_shelve_read(self, *_):
|
def test_shelve_read(self, *_):
|
||||||
"""special behavior for the read shelf"""
|
"""special behavior for the read shelf"""
|
||||||
shelf = models.Shelf.objects.get(identifier="read")
|
shelf = models.Shelf.objects.get(identifier="read")
|
||||||
request = self.factory.post(
|
request = self.factory.post(
|
||||||
|
@ -179,7 +191,7 @@ class ShelfViews(TestCase):
|
||||||
# make sure the book is on the shelf
|
# make sure the book is on the shelf
|
||||||
self.assertEqual(shelf.books.get(), self.book)
|
self.assertEqual(shelf.books.get(), self.book)
|
||||||
|
|
||||||
def test_handle_unshelve(self, *_):
|
def test_unshelve(self, *_):
|
||||||
"""remove a book from a shelf"""
|
"""remove a book from a shelf"""
|
||||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||||
models.ShelfBook.objects.create(
|
models.ShelfBook.objects.create(
|
||||||
|
@ -197,3 +209,76 @@ class ShelfViews(TestCase):
|
||||||
self.assertEqual(activity["type"], "Remove")
|
self.assertEqual(activity["type"], "Remove")
|
||||||
self.assertEqual(activity["object"]["id"], item.remote_id)
|
self.assertEqual(activity["object"]["id"], item.remote_id)
|
||||||
self.assertEqual(self.shelf.books.count(), 0)
|
self.assertEqual(self.shelf.books.count(), 0)
|
||||||
|
|
||||||
|
def test_create_shelf(self, *_):
|
||||||
|
"""a brand new custom shelf"""
|
||||||
|
form = forms.ShelfForm()
|
||||||
|
form.data["user"] = self.local_user.id
|
||||||
|
form.data["name"] = "new shelf name"
|
||||||
|
form.data["description"] = "desc"
|
||||||
|
form.data["privacy"] = "unlisted"
|
||||||
|
request = self.factory.post("", form.data)
|
||||||
|
request.user = self.local_user
|
||||||
|
|
||||||
|
views.create_shelf(request)
|
||||||
|
|
||||||
|
shelf = models.Shelf.objects.get(name="new shelf name")
|
||||||
|
self.assertEqual(shelf.privacy, "unlisted")
|
||||||
|
self.assertEqual(shelf.description, "desc")
|
||||||
|
self.assertEqual(shelf.user, self.local_user)
|
||||||
|
|
||||||
|
def test_delete_shelf(self, *_):
|
||||||
|
"""delete a brand new custom shelf"""
|
||||||
|
request = self.factory.post("")
|
||||||
|
request.user = self.local_user
|
||||||
|
shelf_id = self.shelf.id
|
||||||
|
|
||||||
|
views.delete_shelf(request, shelf_id)
|
||||||
|
|
||||||
|
self.assertFalse(models.Shelf.objects.filter(id=shelf_id).exists())
|
||||||
|
|
||||||
|
def test_delete_shelf_unauthorized(self, *_):
|
||||||
|
"""delete a brand new custom shelf"""
|
||||||
|
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||||
|
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||||
|
):
|
||||||
|
rat = models.User.objects.create_user(
|
||||||
|
"rat@local.com",
|
||||||
|
"rat@mouse.mouse",
|
||||||
|
"password",
|
||||||
|
local=True,
|
||||||
|
localname="rat",
|
||||||
|
)
|
||||||
|
request = self.factory.post("")
|
||||||
|
request.user = rat
|
||||||
|
|
||||||
|
with self.assertRaises(PermissionDenied):
|
||||||
|
views.delete_shelf(request, self.shelf.id)
|
||||||
|
|
||||||
|
self.assertTrue(models.Shelf.objects.filter(id=self.shelf.id).exists())
|
||||||
|
|
||||||
|
def test_delete_shelf_has_book(self, *_):
|
||||||
|
"""delete a brand new custom shelf"""
|
||||||
|
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||||
|
models.ShelfBook.objects.create(
|
||||||
|
book=self.book, user=self.local_user, shelf=self.shelf
|
||||||
|
)
|
||||||
|
request = self.factory.post("")
|
||||||
|
request.user = self.local_user
|
||||||
|
|
||||||
|
with self.assertRaises(PermissionDenied):
|
||||||
|
views.delete_shelf(request, self.shelf.id)
|
||||||
|
|
||||||
|
self.assertTrue(models.Shelf.objects.filter(id=self.shelf.id).exists())
|
||||||
|
|
||||||
|
def test_delete_shelf_not_editable(self, *_):
|
||||||
|
"""delete a brand new custom shelf"""
|
||||||
|
shelf = self.local_user.shelf_set.first()
|
||||||
|
self.assertFalse(shelf.editable)
|
||||||
|
request = self.factory.post("")
|
||||||
|
request.user = self.local_user
|
||||||
|
|
||||||
|
with self.assertRaises(PermissionDenied):
|
||||||
|
views.delete_shelf(request, shelf.id)
|
||||||
|
|
||||||
|
self.assertTrue(models.Shelf.objects.filter(id=shelf.id).exists())
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
""" test for app action functionality """
|
""" test for app action functionality """
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
from tidylib import tidy_document
|
||||||
|
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.http.response import Http404
|
from django.http.response import Http404
|
||||||
|
@ -55,7 +56,16 @@ class UserViews(TestCase):
|
||||||
is_api.return_value = False
|
is_api.return_value = False
|
||||||
result = view(request, "mouse")
|
result = view(request, "mouse")
|
||||||
self.assertIsInstance(result, TemplateResponse)
|
self.assertIsInstance(result, TemplateResponse)
|
||||||
result.render()
|
html = result.render()
|
||||||
|
_, errors = tidy_document(
|
||||||
|
html.content,
|
||||||
|
options={
|
||||||
|
"drop-empty-elements": False,
|
||||||
|
"warn-proprietary-attributes": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if errors:
|
||||||
|
raise Exception(errors)
|
||||||
self.assertEqual(result.status_code, 200)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
request.user = self.anonymous_user
|
request.user = self.anonymous_user
|
||||||
|
@ -63,7 +73,16 @@ class UserViews(TestCase):
|
||||||
is_api.return_value = False
|
is_api.return_value = False
|
||||||
result = view(request, "mouse")
|
result = view(request, "mouse")
|
||||||
self.assertIsInstance(result, TemplateResponse)
|
self.assertIsInstance(result, TemplateResponse)
|
||||||
result.render()
|
html = result.render()
|
||||||
|
_, errors = tidy_document(
|
||||||
|
html.content,
|
||||||
|
options={
|
||||||
|
"drop-empty-elements": False,
|
||||||
|
"warn-proprietary-attributes": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if errors:
|
||||||
|
raise Exception(errors)
|
||||||
self.assertEqual(result.status_code, 200)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
with patch("bookwyrm.views.user.is_api_request") as is_api:
|
with patch("bookwyrm.views.user.is_api_request") as is_api:
|
||||||
|
@ -92,7 +111,16 @@ class UserViews(TestCase):
|
||||||
is_api.return_value = False
|
is_api.return_value = False
|
||||||
result = view(request, "mouse")
|
result = view(request, "mouse")
|
||||||
self.assertIsInstance(result, TemplateResponse)
|
self.assertIsInstance(result, TemplateResponse)
|
||||||
result.render()
|
html = result.render()
|
||||||
|
_, errors = tidy_document(
|
||||||
|
html.content,
|
||||||
|
options={
|
||||||
|
"drop-empty-elements": False,
|
||||||
|
"warn-proprietary-attributes": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if errors:
|
||||||
|
raise Exception(errors)
|
||||||
self.assertEqual(result.status_code, 200)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
with patch("bookwyrm.views.user.is_api_request") as is_api:
|
with patch("bookwyrm.views.user.is_api_request") as is_api:
|
||||||
|
@ -123,7 +151,16 @@ class UserViews(TestCase):
|
||||||
is_api.return_value = False
|
is_api.return_value = False
|
||||||
result = view(request, "mouse")
|
result = view(request, "mouse")
|
||||||
self.assertIsInstance(result, TemplateResponse)
|
self.assertIsInstance(result, TemplateResponse)
|
||||||
result.render()
|
html = result.render()
|
||||||
|
_, errors = tidy_document(
|
||||||
|
html.content,
|
||||||
|
options={
|
||||||
|
"drop-empty-elements": False,
|
||||||
|
"warn-proprietary-attributes": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if errors:
|
||||||
|
raise Exception(errors)
|
||||||
self.assertEqual(result.status_code, 200)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
with patch("bookwyrm.views.user.is_api_request") as is_api:
|
with patch("bookwyrm.views.user.is_api_request") as is_api:
|
||||||
|
|
|
@ -41,7 +41,7 @@ class Goal(View):
|
||||||
"year": year,
|
"year": year,
|
||||||
"is_self": request.user == user,
|
"is_self": request.user == user,
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, "goal.html", data)
|
return TemplateResponse(request, "user/goal.html", data)
|
||||||
|
|
||||||
def post(self, request, username, year):
|
def post(self, request, username, year):
|
||||||
"""update or create an annual goal"""
|
"""update or create an annual goal"""
|
||||||
|
@ -58,7 +58,7 @@ class Goal(View):
|
||||||
"goal": goal,
|
"goal": goal,
|
||||||
"year": year,
|
"year": year,
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, "goal.html", data)
|
return TemplateResponse(request, "user/goal.html", data)
|
||||||
goal = form.save()
|
goal = form.save()
|
||||||
|
|
||||||
if request.POST.get("post-status"):
|
if request.POST.get("post-status"):
|
||||||
|
|
|
@ -85,12 +85,14 @@ class Shelf(View):
|
||||||
"shelves": shelves,
|
"shelves": shelves,
|
||||||
"shelf": shelf,
|
"shelf": shelf,
|
||||||
"books": page,
|
"books": page,
|
||||||
|
"edit_form": forms.ShelfForm(instance=shelf if shelf_identifier else None),
|
||||||
|
"create_form": forms.ShelfForm(),
|
||||||
"page_range": paginated.get_elided_page_range(
|
"page_range": paginated.get_elided_page_range(
|
||||||
page.number, on_each_side=2, on_ends=1
|
page.number, on_each_side=2, on_ends=1
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
return TemplateResponse(request, "user/shelf/shelf.html", data)
|
return TemplateResponse(request, "shelf/shelf.html", data)
|
||||||
|
|
||||||
@method_decorator(login_required, name="dispatch")
|
@method_decorator(login_required, name="dispatch")
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
@ -128,7 +130,7 @@ def create_shelf(request):
|
||||||
def delete_shelf(request, shelf_id):
|
def delete_shelf(request, shelf_id):
|
||||||
"""user generated shelves"""
|
"""user generated shelves"""
|
||||||
shelf = get_object_or_404(models.Shelf, id=shelf_id)
|
shelf = get_object_or_404(models.Shelf, id=shelf_id)
|
||||||
shelf.raise_not_deletable()
|
shelf.raise_not_deletable(request.user)
|
||||||
|
|
||||||
shelf.delete()
|
shelf.delete()
|
||||||
return redirect("user-shelves", request.user.localname)
|
return redirect("user-shelves", request.user.localname)
|
||||||
|
|
Loading…
Reference in a new issue