forked from mirrors/bookwyrm
Merge branch 'misc-tests' into user-view
This commit is contained in:
commit
778975d492
39 changed files with 1516 additions and 867 deletions
|
@ -8,6 +8,8 @@ USE_HTTPS=true
|
|||
DOMAIN=your.domain.here
|
||||
EMAIL=your@email.here
|
||||
|
||||
# Instance defualt language (see options at bookwyrm/settings.py "LANGUAGES"
|
||||
LANGUAGE_CODE="en-us"
|
||||
# Used for deciding which editions to prefer
|
||||
DEFAULT_LANGUAGE="English"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -19,9 +19,7 @@ def init_permissions():
|
|||
{
|
||||
"codename": "edit_instance_settings",
|
||||
"name": "change the instance info",
|
||||
"groups": [
|
||||
"admin",
|
||||
],
|
||||
"groups": ["admin"],
|
||||
},
|
||||
{
|
||||
"codename": "set_user_group",
|
||||
|
@ -66,15 +64,12 @@ def init_permissions():
|
|||
for group_name in permission["groups"]:
|
||||
Group.objects.get(name=group_name).permissions.add(permission_obj)
|
||||
|
||||
# while the groups and permissions shouldn't be changed because the code
|
||||
# depends on them, what permissions go with what groups should be editable
|
||||
|
||||
|
||||
def init_connectors():
|
||||
"""access book data sources"""
|
||||
models.Connector.objects.create(
|
||||
identifier="bookwyrm.social",
|
||||
name="BookWyrm dot Social",
|
||||
name="Bookwyrm.social",
|
||||
connector_file="bookwyrm_connector",
|
||||
base_url="https://bookwyrm.social",
|
||||
books_url="https://bookwyrm.social/book",
|
||||
|
@ -84,6 +79,7 @@ def init_connectors():
|
|||
priority=2,
|
||||
)
|
||||
|
||||
# pylint: disable=line-too-long
|
||||
models.Connector.objects.create(
|
||||
identifier="inventaire.io",
|
||||
name="Inventaire",
|
||||
|
@ -127,7 +123,7 @@ def init_settings():
|
|||
)
|
||||
|
||||
|
||||
def init_link_domains(*_):
|
||||
def init_link_domains():
|
||||
"""safe book links"""
|
||||
domains = [
|
||||
("standardebooks.org", "Standard EBooks"),
|
||||
|
@ -144,10 +140,15 @@ def init_link_domains(*_):
|
|||
)
|
||||
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
# pylint: disable=unused-argument
|
||||
class Command(BaseCommand):
|
||||
"""command-line options"""
|
||||
|
||||
help = "Initializes the database with starter data"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
"""specify which function to run"""
|
||||
parser.add_argument(
|
||||
"--limit",
|
||||
default=None,
|
||||
|
@ -155,6 +156,7 @@ class Command(BaseCommand):
|
|||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""execute init"""
|
||||
limit = options.get("limit")
|
||||
tables = [
|
||||
"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")
|
||||
|
|
|
@ -14,7 +14,7 @@ VERSION = "0.2.0"
|
|||
PAGE_LENGTH = env("PAGE_LENGTH", 15)
|
||||
DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English")
|
||||
|
||||
JS_CACHE = "76c5ff1f"
|
||||
JS_CACHE = "7b5303af"
|
||||
|
||||
# email
|
||||
EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend")
|
||||
|
@ -243,7 +243,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = "en-us"
|
||||
LANGUAGE_CODE = env("LANGUAGE_CODE", "en-us")
|
||||
LANGUAGES = [
|
||||
("en-us", _("English")),
|
||||
("de-de", _("Deutsch (German)")),
|
||||
|
|
|
@ -122,39 +122,13 @@ let BookWyrm = new (class {
|
|||
*/
|
||||
updateCountElement(counter, data) {
|
||||
let count = data.count;
|
||||
const count_by_type = data.count_by_type;
|
||||
|
||||
if (count === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentCount = counter.innerText;
|
||||
const hasMentions = data.has_mentions;
|
||||
const allowedStatusTypesEl = document.getElementById("unread-notifications-wrapper");
|
||||
|
||||
// If we're on the right counter element
|
||||
if (counter.closest("[data-poll-wrapper]").contains(allowedStatusTypesEl)) {
|
||||
const allowedStatusTypes = JSON.parse(allowedStatusTypesEl.textContent);
|
||||
|
||||
// For keys in common between allowedStatusTypes and count_by_type
|
||||
// This concerns 'review', 'quotation', 'comment'
|
||||
count = allowedStatusTypes.reduce(function (prev, currentKey) {
|
||||
const currentValue = count_by_type[currentKey] | 0;
|
||||
|
||||
return prev + currentValue;
|
||||
}, 0);
|
||||
|
||||
// Add all the "other" in count_by_type if 'everything' is allowed
|
||||
if (allowedStatusTypes.includes("everything")) {
|
||||
// Clone count_by_type with 0 for reviews/quotations/comments
|
||||
const count_by_everything_else = Object.assign({}, count_by_type, {
|
||||
review: 0,
|
||||
quotation: 0,
|
||||
comment: 0,
|
||||
});
|
||||
|
||||
count = Object.keys(count_by_everything_else).reduce(function (prev, currentKey) {
|
||||
const currentValue = count_by_everything_else[currentKey] | 0;
|
||||
|
||||
return prev + currentValue;
|
||||
}, count);
|
||||
}
|
||||
}
|
||||
|
||||
if (count != currentCount) {
|
||||
this.addRemoveClass(counter.closest("[data-poll-wrapper]"), "is-hidden", count < 1);
|
||||
|
@ -517,7 +491,7 @@ let BookWyrm = new (class {
|
|||
|
||||
duplicateInput(event) {
|
||||
const trigger = event.currentTarget;
|
||||
const input_id = trigger.dataset["duplicate"];
|
||||
const input_id = trigger.dataset.duplicate;
|
||||
const orig = document.getElementById(input_id);
|
||||
const parent = orig.parentNode;
|
||||
const new_count = parent.querySelectorAll("input").length + 1;
|
||||
|
|
|
@ -24,9 +24,12 @@
|
|||
|
||||
{# announcements and system messages #}
|
||||
{% if not activities.number > 1 %}
|
||||
<a href="{{ request.path }}" class="transition-y is-hidden notification is-primary is-block" data-poll-wrapper>
|
||||
{% blocktrans with tab_key=tab.key %}load <span data-poll="stream/{{ tab_key }}">0</span> unread status(es){% endblocktrans %}
|
||||
{{ allowed_status_types|json_script:"unread-notifications-wrapper" }}
|
||||
<a
|
||||
href="{{ request.path }}"
|
||||
class="transition-y is-hidden notification is-primary is-block"
|
||||
data-poll-wrapper
|
||||
>
|
||||
<span data-poll="stream/{{ tab.key }}"></span>
|
||||
</a>
|
||||
|
||||
{% if request.user.show_goal and not goal and tab.key == 'home' %}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<div class="modal is-active" role="dialog" aria-modal="true" aria-labelledby="get_started_header">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card is-fullwidth">
|
||||
<header class="modal-card-head">
|
||||
<header class="modal-card-head navbar">
|
||||
<img
|
||||
class="image logo mr-2"
|
||||
src="{% if site.logo_small %}{% get_media_prefix %}{{ site.logo_small }}{% else %}{% static "images/logo-small.png" %}{% endif %}"
|
||||
|
|
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,5 +1,78 @@
|
|||
# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
|
||||
|
||||
##### AhrefsBot #####
|
||||
# see http://ahrefs.com/robot/
|
||||
User-agent: AhrefsBot
|
||||
Crawl-Delay: 10
|
||||
#Disallow: /
|
||||
|
||||
|
||||
##### SemrushBot #####
|
||||
# see http://www.semrush.com/bot.html
|
||||
User-agent: SemrushBot
|
||||
Crawl-Delay: 10
|
||||
#Disallow: /
|
||||
|
||||
# To block SemrushBot from crawling your site for different SEO and technical issues:
|
||||
User-agent: SiteAuditBot
|
||||
Disallow: /
|
||||
|
||||
#To block SemrushBot from crawling your site for Backlink Audit tool:
|
||||
User-agent: SemrushBot-BA
|
||||
Disallow: /
|
||||
|
||||
#To block SemrushBot from crawling your site for On Page SEO Checker tool and similar tools:
|
||||
User-agent: SemrushBot-SI
|
||||
Disallow: /
|
||||
|
||||
#To block SemrushBot from checking URLs on your site for SWA tool:
|
||||
User-agent: SemrushBot-SWA
|
||||
Disallow: /
|
||||
|
||||
#To block SemrushBot from crawling your site for Content Analyzer and Post Tracking tools:
|
||||
User-agent: SemrushBot-CT
|
||||
Disallow: /
|
||||
|
||||
#To block SemrushBot from crawling your site for Brand Monitoring:
|
||||
User-agent: SemrushBot-BM
|
||||
Disallow: /
|
||||
|
||||
#To block SplitSignalBot from crawling your site for SplitSignal tool:
|
||||
User-agent: SplitSignalBot
|
||||
Disallow: /
|
||||
|
||||
#To block SemrushBot-COUB from crawling your site for Content Outline Builder tool:
|
||||
User-agent: SemrushBot-COUB
|
||||
Disallow: /
|
||||
|
||||
|
||||
##### DotBot #####
|
||||
# see https://opensiteexplorer.org/dotbot
|
||||
User-agent: dotbot
|
||||
Crawl-delay: 10
|
||||
#Disallow: /
|
||||
|
||||
|
||||
##### BLEXBot #####
|
||||
# see http://webmeup-crawler.com/
|
||||
User-agent: BLEXBot
|
||||
Crawl-delay: 10
|
||||
#Disallow: /
|
||||
|
||||
|
||||
##### MJ12bot #####
|
||||
# see http://mj12bot.com/
|
||||
User-Agent: MJ12bot
|
||||
Crawl-Delay: 20
|
||||
#Disallow: /
|
||||
|
||||
|
||||
##### PetalBot #####
|
||||
# see https://webmaster.petalsearch.com/site/petalbot
|
||||
User-agent: PetalBot
|
||||
Disallow: /
|
||||
|
||||
|
||||
User-agent: *
|
||||
Disallow: /static/js/
|
||||
Disallow: /static/css/
|
||||
|
|
|
@ -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 }}"
|
||||
>
|
||||
|
|
113
bookwyrm/tests/management/test_initdb.py
Normal file
113
bookwyrm/tests/management/test_initdb.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
""" test populating user streams """
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from django.test import TestCase
|
||||
|
||||
from bookwyrm import models
|
||||
from bookwyrm.management.commands import initdb
|
||||
|
||||
|
||||
class InitDB(TestCase):
|
||||
"""gotta init that db"""
|
||||
|
||||
def test_init_groups(self):
|
||||
"""Create groups"""
|
||||
initdb.init_groups()
|
||||
self.assertEqual(Group.objects.count(), 3)
|
||||
self.assertTrue(Group.objects.filter(name="admin").exists())
|
||||
self.assertTrue(Group.objects.filter(name="moderator").exists())
|
||||
self.assertTrue(Group.objects.filter(name="editor").exists())
|
||||
|
||||
def test_init_permissions(self):
|
||||
"""User permissions"""
|
||||
initdb.init_groups()
|
||||
initdb.init_permissions()
|
||||
|
||||
group = Group.objects.get(name="admin")
|
||||
self.assertTrue(
|
||||
group.permissions.filter(codename="edit_instance_settings").exists()
|
||||
)
|
||||
self.assertTrue(group.permissions.filter(codename="set_user_group").exists())
|
||||
self.assertTrue(
|
||||
group.permissions.filter(codename="control_federation").exists()
|
||||
)
|
||||
self.assertTrue(group.permissions.filter(codename="create_invites").exists())
|
||||
self.assertTrue(group.permissions.filter(codename="moderate_user").exists())
|
||||
self.assertTrue(group.permissions.filter(codename="moderate_post").exists())
|
||||
self.assertTrue(group.permissions.filter(codename="edit_book").exists())
|
||||
|
||||
group = Group.objects.get(name="moderator")
|
||||
self.assertTrue(group.permissions.filter(codename="set_user_group").exists())
|
||||
self.assertTrue(
|
||||
group.permissions.filter(codename="control_federation").exists()
|
||||
)
|
||||
self.assertTrue(group.permissions.filter(codename="create_invites").exists())
|
||||
self.assertTrue(group.permissions.filter(codename="moderate_user").exists())
|
||||
self.assertTrue(group.permissions.filter(codename="moderate_post").exists())
|
||||
self.assertTrue(group.permissions.filter(codename="edit_book").exists())
|
||||
|
||||
group = Group.objects.get(name="editor")
|
||||
self.assertTrue(group.permissions.filter(codename="edit_book").exists())
|
||||
|
||||
def test_init_connectors(self):
|
||||
"""Outside data sources"""
|
||||
initdb.init_connectors()
|
||||
self.assertTrue(
|
||||
models.Connector.objects.filter(identifier="bookwyrm.social").exists()
|
||||
)
|
||||
self.assertTrue(
|
||||
models.Connector.objects.filter(identifier="inventaire.io").exists()
|
||||
)
|
||||
self.assertTrue(
|
||||
models.Connector.objects.filter(identifier="openlibrary.org").exists()
|
||||
)
|
||||
|
||||
def test_init_settings(self):
|
||||
"""Create the settings file"""
|
||||
initdb.init_settings()
|
||||
settings = models.SiteSettings.objects.get()
|
||||
self.assertEqual(settings.name, "BookWyrm")
|
||||
|
||||
def test_init_link_domains(self):
|
||||
"""Common trusted domains for links"""
|
||||
initdb.init_link_domains()
|
||||
self.assertTrue(
|
||||
models.LinkDomain.objects.filter(
|
||||
status="approved", domain="standardebooks.org"
|
||||
).exists()
|
||||
)
|
||||
self.assertTrue(
|
||||
models.LinkDomain.objects.filter(
|
||||
status="approved", domain="theanarchistlibrary.org"
|
||||
).exists()
|
||||
)
|
||||
|
||||
def test_command_no_args(self):
|
||||
"""command line calls"""
|
||||
command = initdb.Command()
|
||||
command.handle()
|
||||
|
||||
# everything should have been called
|
||||
self.assertEqual(Group.objects.count(), 3)
|
||||
self.assertTrue(Permission.objects.exists())
|
||||
self.assertEqual(models.Connector.objects.count(), 3)
|
||||
self.assertEqual(models.FederatedServer.objects.count(), 2)
|
||||
self.assertEqual(models.SiteSettings.objects.count(), 1)
|
||||
self.assertEqual(models.LinkDomain.objects.count(), 5)
|
||||
|
||||
def test_command_with_args(self):
|
||||
"""command line calls"""
|
||||
command = initdb.Command()
|
||||
command.handle(limit="group")
|
||||
|
||||
# everything should have been called
|
||||
self.assertEqual(Group.objects.count(), 3)
|
||||
self.assertEqual(models.Connector.objects.count(), 0)
|
||||
self.assertEqual(models.FederatedServer.objects.count(), 0)
|
||||
self.assertEqual(models.SiteSettings.objects.count(), 0)
|
||||
self.assertEqual(models.LinkDomain.objects.count(), 0)
|
||||
|
||||
def test_command_invalid_args(self):
|
||||
"""command line calls"""
|
||||
command = initdb.Command()
|
||||
with self.assertRaises(Exception):
|
||||
command.handle(limit="sdkfjhsdkjf")
|
|
@ -1,4 +1,5 @@
|
|||
""" test for app action functionality """
|
||||
import os
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
|
@ -207,3 +208,6 @@ class FederationViews(TestCase):
|
|||
created = models.FederatedServer.objects.get(server_name="server.name")
|
||||
self.assertEqual(created.status, "blocked")
|
||||
self.assertEqual(created.notes, "https://explanation.url")
|
||||
|
||||
# remove file.json after test
|
||||
os.remove("file.json")
|
||||
|
|
|
@ -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")
|
|
@ -140,3 +140,14 @@ class AnnualSummary(TestCase):
|
|||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_personal_annual_summary(self, *_):
|
||||
"""redirect to unique user url"""
|
||||
view = views.personal_annual_summary
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
result = view(request, 2020)
|
||||
|
||||
self.assertEqual(result.status_code, 302)
|
||||
self.assertEqual(result.url, "/user/mouse/2020-in-the-books")
|
||||
|
|
|
@ -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)
|
|
@ -45,31 +45,51 @@ class UpdateViews(TestCase):
|
|||
data = json.loads(result.getvalue())
|
||||
self.assertEqual(data["count"], 1)
|
||||
|
||||
def test_get_unread_status_count(self):
|
||||
def test_get_unread_status_string(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
with patch(
|
||||
"bookwyrm.activitystreams.ActivityStream.get_unread_count"
|
||||
) as mock_count:
|
||||
with patch(
|
||||
# pylint:disable=line-too-long
|
||||
"bookwyrm.activitystreams.ActivityStream.get_unread_count_by_status_type"
|
||||
) as mock_count_by_status:
|
||||
mock_count.return_value = 3
|
||||
mock_count_by_status.return_value = {"review": 5}
|
||||
result = views.get_unread_status_count(request, "home")
|
||||
) as mock_count, patch(
|
||||
"bookwyrm.activitystreams.ActivityStream.get_unread_count_by_status_type"
|
||||
) as mock_count_by_status:
|
||||
mock_count.return_value = 3
|
||||
mock_count_by_status.return_value = {"review": 5}
|
||||
result = views.get_unread_status_string(request, "home")
|
||||
|
||||
self.assertIsInstance(result, JsonResponse)
|
||||
data = json.loads(result.getvalue())
|
||||
self.assertEqual(data["count"], 3)
|
||||
self.assertEqual(data["count_by_type"]["review"], 5)
|
||||
self.assertEqual(data["count"], "Load 5 unread statuses")
|
||||
|
||||
def test_get_unread_status_count_invalid_stream(self):
|
||||
def test_get_unread_status_string_with_filters(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
self.local_user.feed_status_types = ["comment", "everything"]
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
with patch(
|
||||
"bookwyrm.activitystreams.ActivityStream.get_unread_count"
|
||||
) as mock_count, patch(
|
||||
"bookwyrm.activitystreams.ActivityStream.get_unread_count_by_status_type"
|
||||
) as mock_count_by_status:
|
||||
mock_count.return_value = 3
|
||||
mock_count_by_status.return_value = {
|
||||
"generated_note": 1,
|
||||
"comment": 1,
|
||||
"review": 10,
|
||||
}
|
||||
result = views.get_unread_status_string(request, "home")
|
||||
|
||||
self.assertIsInstance(result, JsonResponse)
|
||||
data = json.loads(result.getvalue())
|
||||
self.assertEqual(data["count"], "Load 2 unread statuses")
|
||||
|
||||
def test_get_unread_status_string_invalid_stream(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
with self.assertRaises(Http404):
|
||||
views.get_unread_status_count(request, "fish")
|
||||
views.get_unread_status_string(request, "fish")
|
||||
|
|
|
@ -47,7 +47,9 @@ urlpatterns = [
|
|||
re_path(r"^ostatus_subscribe/?$", views.ostatus_follow_request),
|
||||
# polling updates
|
||||
re_path("^api/updates/notifications/?$", views.get_notification_count),
|
||||
re_path("^api/updates/stream/(?P<stream>[a-z]+)/?$", views.get_unread_status_count),
|
||||
re_path(
|
||||
"^api/updates/stream/(?P<stream>[a-z]+)/?$", views.get_unread_status_string
|
||||
),
|
||||
# authentication
|
||||
re_path(r"^login/?$", views.Login.as_view(), name="login"),
|
||||
re_path(r"^login/(?P<confirmed>confirmed)/?$", views.Login.as_view(), name="login"),
|
||||
|
@ -357,16 +359,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
|
||||
|
@ -101,7 +114,7 @@ from .rss_feed import RssFeed
|
|||
from .search import Search
|
||||
from .status import CreateStatus, EditStatus, DeleteStatus, update_progress
|
||||
from .status import edit_readthrough
|
||||
from .updates import get_notification_count, get_unread_status_count
|
||||
from .updates import get_notification_count, get_unread_status_string
|
||||
from .user import User, Followers, Following, hide_suggestions, user_redirect
|
||||
from .wellknown import *
|
||||
from .annual_summary import (
|
||||
|
|
|
@ -62,7 +62,6 @@ class Feed(View):
|
|||
"streams": STREAMS,
|
||||
"goal_form": forms.GoalForm(),
|
||||
"feed_status_types_options": FeedFilterChoices,
|
||||
"allowed_status_types": request.user.feed_status_types,
|
||||
"filters_applied": filters_applied,
|
||||
"path": f"/{tab['key']}",
|
||||
"annual_summary_year": get_annual_summary_year(),
|
||||
|
|
|
@ -57,14 +57,7 @@ class GetStartedBooks(View):
|
|||
if len(book_results) < 5:
|
||||
popular_books = (
|
||||
models.Edition.objects.exclude(
|
||||
# exclude already shelved
|
||||
Q(
|
||||
parent_work__in=[
|
||||
b.book.parent_work
|
||||
for b in request.user.shelfbook_set.distinct().all()
|
||||
]
|
||||
)
|
||||
| Q( # and exclude if it's already in search results
|
||||
Q( # exclude if it's already in search results
|
||||
parent_work__in=[b.parent_work for b in book_results]
|
||||
)
|
||||
)
|
||||
|
|
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)
|
|
@ -1,6 +1,7 @@
|
|||
""" endpoints for getting updates about activity """
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import Http404, JsonResponse
|
||||
from django.utils.translation import ngettext
|
||||
|
||||
from bookwyrm import activitystreams
|
||||
|
||||
|
@ -17,14 +18,31 @@ def get_notification_count(request):
|
|||
|
||||
|
||||
@login_required
|
||||
def get_unread_status_count(request, stream="home"):
|
||||
def get_unread_status_string(request, stream="home"):
|
||||
"""any unread statuses for this feed?"""
|
||||
stream = activitystreams.streams.get(stream)
|
||||
if not stream:
|
||||
raise Http404
|
||||
return JsonResponse(
|
||||
{
|
||||
"count": stream.get_unread_count(request.user),
|
||||
"count_by_type": stream.get_unread_count_by_status_type(request.user),
|
||||
}
|
||||
)
|
||||
|
||||
counts_by_type = stream.get_unread_count_by_status_type(request.user).items()
|
||||
if counts_by_type == {}:
|
||||
count = stream.get_unread_count(request.user)
|
||||
else:
|
||||
# only consider the types that are visible in the feed
|
||||
allowed_status_types = request.user.feed_status_types
|
||||
count = sum(c for (k, c) in counts_by_type if k in allowed_status_types)
|
||||
# if "everything else" is allowed, add other types to the sum
|
||||
count += sum(
|
||||
c
|
||||
for (k, c) in counts_by_type
|
||||
if k not in ["review", "comment", "quotation"]
|
||||
)
|
||||
|
||||
if not count:
|
||||
return JsonResponse({})
|
||||
|
||||
translation_string = lambda c: ngettext(
|
||||
"Load %(count)d unread status", "Load %(count)d unread statuses", c
|
||||
) % {"count": c}
|
||||
|
||||
return JsonResponse({"count": translation_string(count)})
|
||||
|
|
Loading…
Reference in a new issue